Top Menu

quickP2P API (java/android SDK version)  - programming instructions

 AUXILIARY MATERIAL:

 - "qp2p_SimplestDemo_FileTransfer"  demo eclipse android application project

 quickP2P API (java/android SDK version)  - programming instructions

Starting with java/android SDK API (with android SDK samples)

1. quickP2P lib delegate/event system

1.1 Event s in quickP2P API

1.2 Callbacks in quickP2P API

1.3 Event/Callback handler execution

2. Peer session creation

3. Peer lookup queries

4. Peer state tracing / changes notifications

5. Instant messages

6. Direct communication tunnel creation between peers

7. Using virtual index storage

8. Security, key exchange and data encryption/decryption

9. Required app permissions

 10. Complete list of delegates interfaces used in ConnectionManager with their in-prints

 

1. quickP2P lib delegate/event system

 

qP2Plib java API delegate/event system  is based on delegates. Since java supports anonymous objects creation this becomes quite usable just watch out for garbage collector when using anonymous objects. You need to provide object holding right named method to serve as delegate to event/callback handler. We provided interfaces your object can implement those methods from but important to note is fact that this is not necessary. Method needs to have right name and right arguments to be invoked by event/callback in delegate object.

 

*note: One problem we have with this currently is lack of ability of easy code fuscation because of method name textual bindings. Fuscation will change method names and JNI wrapper will be unable to find them. This still can be achieved if you close all qp2p API interaction in separate classes then you can just omit those classes from fuscation.  

 

Now we will show you same basic examples of using events/callbacks and there is no better way that giving simple examples.  Once hooked even handler will be executed every time corresponding event triggers until we un-hook it manually or simply destroy objects in contrary callback is usually passed as some method argument and will execute only once to handle particular method execution asynchronous response .

 

1.1 Event s in quickP2P API

 

- When you hook event once it will execute response every time correspondent request arrives until you unhook it.

 

- If you have both event hooked and callback provided to execute on request completion first event is triggered then callback

 

We will now give you examples of hooking events. Here is example of hooking event to handler that is member of calling class:

 

public class TestActivity extends Activity implements

qp2plib.ConnectionManager.iPeerConnect_delegate {

private qp2plib.ConnectionManager cm = null;

@Override

protected void onCreate(Bundle savedInstanceState) {  

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

//we create new instance of connection manager class

cm = new qp2plib.ConnectionManager(new InetSocketAddress("supernode1.p2p-api.com",80));

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerConnected.getCode(), this);

}

//this is implemented method qp2plib.ConnectionManager.iPeerConnect_delegate

public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) throws IOException {

       //... handler body.

}

}

 

 

 

Here is how we would hook same event to anonymous object method:

 

public class TestActivity extends Activity {

private qp2plib.ConnectionManager cm = null;

@Override

protected void onCreate(Bundle savedInstanceState) {  

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_test);

//we create new instance of connection manager class

cm = new qp2plib.ConnectionManager(new InetSocketAddress("supernode1.p2p-api.com",80));

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerConnected.getCode(),

new qp2plib.ConnectionManager.iPeerConnect_delegate(){

       public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) throws IOException  {

              //... handler body.

       }

});

}

}

 

Reference to delegate object will be kept in inner event/callback object so that will prevent garbage collector from destroying anonymous object that serves as delegate.

 

 1.2 Callbacks in quickP2P API

 

Callbacks are usually given as arguments of functions having unpredictable time for completion to trigger when that result becomes available. There are related only to particular function call / response.

 

When some event handler is set and we also have callback defend to trigger on same particular event both event handler and callback handler will be executed first event handler then callback handler.

 

Now here is use of callbacks demonstration on cm.Connect method.  Callback delegate is last argument of ConnectionManager.connect method. This callback executes when nat-traversal operation completes, here is callback handler in anonymous object:

 

cm.Connect(remotePeer, ConnectionType.TCP.getCode() , null, new  

 

ConnectionManager.iPeerConnectCallback_delegate(){

        public void onConnectionManager_PeerConnectCallback(Peer peer,

              Guid PeerCheckpointUID, Guid PeerUID, Guid TransactionUID,

              boolean Success, int PeerConnectFailureReason_code) {

              //...

              peer.getTunnelSocket().getOutputStream().writeBytes("Hello through NAT!");

              //...

                          

       }

});

 

Reference to delegate object will be kept in inner event/callback object so that will prevent garbage collector from destroying anonymous object that serves as delegate.

 

And here would be example using calling class as delegate with appropriate method defined in it:

 

cm.Connect(remotePeer, ConnectionType.TCP.getCode() , null, this);

….

//this is implemented method qp2plib.ConnectionManager.iPeerConnect_delegate

public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) {

       //...

       peer.getTunnelSocket().getOutputStream().writeBytes("Hello through NAT!");

       //...

}

 

So basically we use common programming patterns you probably used by now in java and android SDK. Important thing to note about event and callback handlers triggered by quickP2P API is that  they will be executed in “some” thread api may create on need and that thread will probably not be UI thread, so watch out when interacting with UI elements you need to transfer UI directives to UI thread, this is rule that stand for all platforms/languages not just for java. Consider following example:

 

public void onConnectionManager_SessionOpenCallback(ConnectionManager sender,boolean success,int SessionFailReason_code){

   final int code_val = SessionFailReason_code;

   runOnUiThread(

       new Runnable() {

              private int code = code_val;

              public void run() {

               ((TextView) findViewById( R.id.textView1)).setText("Session opened!");

           }

         }

       );

      cm.Query("\"Properties.some_meta\":\"test456\"",null, 0, 0);

    }

 

We want to set text for textView1 textbox , our handler onConnectionManager_SessionOpenCallback will be exacted  from “some” thread so we need to transfer text changing directive to ui thread using runOnUiThread method.

 

 

1.3 Event/Callback handler execution

 

In quickP2P java event/callback handlers are executed in threads API creates by need. This means that if you plan to start some long running operation when even/callback triggers you can just stay in same thread and do tasks.  API will create other new threads if needed for events that happen while that long running tasks is being executed. 

Also there is conservation mechanism, because thread creation in java can be expensive.  After thread is used for handler execution it will not be destroyed for some time in order to possibly execute some new handler if it arrives. 

Fact that event/callback arguments are executed in "some" threads imposes the rule that you can't interact with UI directly from them. You need to transfer execution of UI interaction code to UI thread by using context.runOnUiThread or something similar.

Consider following code form demo project:

Peer remotePeer = peers[0];

//we found our remote peer, now we will create socket tunnel to him

//we found that peer now we will connect to it. Note that track_connect_transaction_uid will match TransactionUID argument in [void onConnectionManager_PeerConnect(...]

Guid track_connect_transaction_uid =

     cm.Connect(remotePeer, ConnectionManager.ConnectionType.TCP.getCode(),null, new ConnectionManager.iPeerConnectCallback_delegate() {//NEXT WE WAIT FOR RESULT IN [void onConnectionManager_PeerConnectCallback(...] in anonymous object below , ON OTHER SIDE  [void onConnectionManager_PeerAccept(...] will trigger instead

public void onConnectionManager_PeerConnectCallback(Peer peer,

                                                    Guid PeerCheckpointUID,

                                                    Guid PeerUID,

                                                    Guid TransactionUID,

                                                    boolean Success,

                                                    int PeerConnectFailureReason_code) {

//...failed handling code omitted...

       if(Success){

//TUNNEL IS CREATED!

//In quickP2P java we can just keep this thread because it is certainly not UI thread and other threads for other

//handlers will be automatically created when needed, so we can continue our long running operation form this thread

       DataInputStream in   = null;

       DataOutputStream out = null;

       try {

              in = new DataInputStream(peer.getTunnelTCPSocket().getInputStream());

              out = new DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());

       } catch (IOException e) {

              try{

                     peer.getTunnelTCPSocket().close();

              }catch (IOException exs) {}

ShowMessageOnUIThread("Error while sending file(s)! Aborted...");

              return;

       }

       ByteBuffer little_endian_buff = ByteBuffer.allocate(8);

       byte[] buff = new byte[8192];

       for(String FileToSend : MainActivity.this.FilesToSend)

       {

              String[] tmp = FileToSend.trim().split(File.separator);

              String name = tmp[tmp.length - 1];

              BufferedInputStream FS = null;

              try {//we open file for reading

                     FS = new BufferedInputStream(new FileInputStream(new File(FileToSend)));                        

                     char[] cname = name.toCharArray();

                     out.write(new byte[] {(byte)cname.length});//we send name length

                     out.writeBytes(name);

                     little_endian_buff.order(ByteOrder.LITTLE_ENDIAN);

                     little_endian_buff.putLong(new File(FileToSend).length());

                     little_endian_buff.rewind();

                     out.write(little_endian_buff.array());//we send file length first

                     int read = 0;

                     //we read and send file blocks

                     while ((read = FS.read(buff, 0, buff.length)) > 0)

                               out.write(buff, 0, read);

                     FS.close();

                     //we wait for some response as confirmation

                     read = in.read(buff);

                     final String fileName = FileToSend;

                     if (read > 0)

                            ShowMessageOnUIThread("File " + fileName +" sent!");

              } catch (IOException e) {

                     ShowMessageOnUIThread("Error while sending file(s)! Aborted...");

                     break;

              }

       }

       try {//we close socket

              in.close();

              out.close();

       } catch (IOException e) {}

       try{

              peer.getTunnelTCPSocket().close();

       }catch (IOException e) {}

       }   

}

} );

We put our code for sending files thru tunneled TCP socket right in handler body and this is ok place because we know this will not block API and that other events happening mean while will be served by other threads AI will create on need.  So we can just place here our possibly long running file transfer operation.  But also note there few calls to ShowMessageOnUIThread( function that look like this:

 

protected void ShowMessageOnUIThread(String message){

       final String msg = message;

       MainActivity.this.runOnUiThread(//Event and callback handlers in quickP2P java are executed in random threads so when we interact with UI we need to send execution to UI thread

              new Runnable() {

                     public void run() {

                           ShowMessage(msg);

                     }

              }

       );

}

 

protected void ShowMessage(String message){

   Toast.makeText(this, message, Toast.LENGTH_SHORT).show();

}

 

We want to show message box to user, and message box is UI element so we send execution of message box creation to UI thread.

 

2. Peer session creation

 

 

For quickP2P client , to be able to do any operation like tunnel opening, sending instant message, searching peers, virtual index operation ... we first need to join abstract peer-to-peer network  like you connect to internet when your computer powers up . Steps for joining application peer to super-node network would be this:

 

1 - Create ConnectionManager object to handle operations,  set required C.M. properties , handles  ...

 

It's always required to set access tokens :

 

cm.setAccessTokens("00000000-XXXX-XXXX-XXXX-XXXXXXXXXXXX","00000000-XXXX-XXXX-XXXX- XXXXXXXXXXXX","");

 

First argument is provider UID , second application UID , third is access key. You can see this values in you provider portal.

 

 

 

2 - Set some meta-data so other peers can find us and we can find them by filtering values

 

cm.getLocalPeer().set("some_meta","bla bla bla");

cm.getLocalPeer().set("email", This email address is being protected from spambots. You need JavaScript enabled to view it.">"someone@somepeople.com");

cm.getLocalPeer().set("gender", "male");

 

you can also alter these properties later while session is opened , you need to call

cm.BrodcastStateChange() to update local peer information on network.

 

3-Initate ConnectionManager.Open(callback)  to open session

 

 

 

4- Wait open session completion handler to execute

 

(note ,after session open completion avoid initiating connect - "tunnel open" operation in next 2-3 sec to give time to api to inspect your network environment properly )

 

Once we have established session with super-node we can:

 

- Query for other peers on network (Peer lookup) with mongo db-like queries in our application scope

 

- Register for peers’ status tracking and receive notifications about changes

 

- Send/Receive instant messages

 

- Open communication tunnels to other peers and other peers can open tunnels to us

 

- Use virtual index manager  if we need it:

 

- Create|Delete|Edit Virtual Networks|Users

 

- Search virtual networks|users with mongo db like queries

 

- Join|Un-join users to networks

 

....

 

We will talk about all of these operations in following texts

 

 

3. Peer lookup queries

 

Peer lookup queries query for peers that are currently online. Query request is initiated using

 

Guid transaction_uid = ConnectionManager.Query(String QueryText , iPeerQueryCompleted_delegate callback_delegate, int page  , int pageLimit );

 

or handy version if we need to check single peer:

 

Guid transaction_uid = ConnectionManager.QuerySingle(Guid PeerUID, iPeerQueryCompleted_delegate callback_delegate);

 

methods of ConnectionManager object.  Callback and event handler method has in-print like this:

 

public interface iPeerQueryCompleted_delegate{

       void onConnectionManager_PeerQueryCompleted(Peer[] peers,int count,boolean Completed, Guid QueryTransactionID,int Page,int PageLimit,int TotalPeers);

}

 

Agruments:

 

FoundPeers List of peers returned by query

Count Number of peers returned by query

Completed If completed then true, not that sometimes result will contain peers but this value will be        false. This can happen for example if 10000000 peers are found by search criteria , and               operation timeout expires before all results are collected

QueryTransactionID Transaction ID of query request

Page Page returned if pagination exists

PageLimit Number of peers per page if pagination exists

TotalPeers Total number of peers matched by query

 

 

 

Event hook example:

...

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerQueryResponse.getCode(), this);

...

public void onConnectionManager_PeerQueryCompleted(Peer[] peers, int count,

                     boolean Completed, Guid QueryTransactionID, int Page,

                     int PageLimit, int TotalPeers) {....

 

Query text format is mongo db like query with the exception that you don't put opening and closing brackets.

 

Let's say we defined some meta properties for our peer like this:

 

cm.getLocalPeer().set("City", "Palo Alto");

cm.getLocalPeer().set("Sex", "female");

cm.getLocalPeer().set("Email",This email address is being protected from spambots. You need JavaScript enabled to view it.">"peer@peers.com");

cm.getLocalPeer().set("Age", "31");

 

Here are some examples of queries that will include this peer in result:

 

Find peer with email This email address is being protected from spambots. You need JavaScript enabled to view it.">"peer@peers.com" that is currently online :

 

cm.Query("\"Properties.Email\"This email address is being protected from spambots. You need JavaScript enabled to view it.">:\"peer@peers.com\"") ;

 

Find peers with email contained in this list This email address is being protected from spambots. You need JavaScript enabled to view it.">[peer@peers.com, This email address is being protected from spambots. You need JavaScript enabled to view it.">mike@peers.com, This email address is being protected from spambots. You need JavaScript enabled to view it.">a123@bla.com] that are currently online:

 

cm.Query("\"Properties.Email\": {$in:[\This email address is being protected from spambots. You need JavaScript enabled to view it.">"peer@peers.com\",This email address is being protected from spambots. You need JavaScript enabled to view it.">\"mike@peers.com\",This email address is being protected from spambots. You need JavaScript enabled to view it.">\"a123@bla.com\"]}");

 

Find female peers form Palo Alto that are currently online:

 

cm.Query("\"Properties.City\":\"Palo Alto\", \"Properties.Sex\":\"female\"") ;

 

Find female peers form Palo Alto with above 30 that are currently online:

 

cm.Query("\"Properties.City\":\"Palo Alto\", \"Properties.Sex\":\"female\", \"Properties.Age\":{$gt:30}");

 

You can test your queries on provider portal. To make more advance queries check out mongo dg query syntax.

 

4. Peer state tracing / changes notifications

 

There is often a need for some peer to have some state on network, like away, busy, available .... You probably seen that as common feature of chat applications. If you think you could achieve same functionality using Instant messages or something else you are right but this provides you more elegant way of transferring such information to peers instantly. Also there are certain situations when this comes handy as irreplaceable feature. Imagine you have some chat like application and you keep your peer buddies in some list or dictionary... your buddy kid  rips power cables from his computer so his application

don't manage to notify you that he is offline. Super-node will notice that in max 90sec, and if you registered for that peer state tracking you will get notification that he is offline.

 

To use this feature you need to hook event to accept notifications:

 

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerStateChanged.getCode(), this);

...

 

public void onConnectionManager_PeerStateChanged(Peer peer, int State) {

                if(State == 0){

//0 - is reserved for disconnected state, 1 - initial connected state, all values > 1 are available for free use

                                ActiveFriends.erase(peer.getUID());//Peer got disconnected we remove it from our internal list

                }

}

 

This is rare case where you don't have option to use callback because you never know when notification will come.

 

Also you have to inform super-nodes that you want to be notified about that peer changes. You do that using following ConnectionManager functions :

 

public Guid RegisterStateTracking(Guid PeerUID);

public Guid RegisterStateTrackingMultiple(Guid[] PeerUIDList);

 

Usually you will call this function in handler of peer query response. From that moment you will receive notifications about state changes of peers that you registered tracking for.  You can also cancel tracking of peers using:

 

public Guid UnregisterStateTracking(Guid PeerUID);

public Guid UnregisterStateTrackingMultiple(Guid[] PeerUIDList);

 

 

 

Common place to place RegisterStateTracking method is in a body of peer query response handler because that is moment when you get fresh information of who is online.  If peer goes offline you will get qp2plib.Peer.DisconnectedState  (=0) state argument value. If he comes back he needs to inform you that he is back online and you again need to register for its state tracking again. So status track is valid until peer goes offline, if he comes back you need to register state tracking again. Also you see there is a need to combine instant messages with this to make it fully usable , why:

 

- You could do peer query and you would get fresh information about online peers , but after 30 sec some peer that should be visible to you by particular application implementation appears on network. You don't know that he is online in that moment and you would not know that until you do peer query again.  But he also does peer query and he knows that you are online so all he has to do is to inform you about that. He can do that by simply sending you some instant message.

 

cm.SendInstantMessage(remotePeer,"Hey, I'am here!", APP_COMMANDS.IM_ONLINE );

 

...

 

Then in receive instant message handler you would know you need to update your peer list.

 

public void onConnectionManager_ReceiveInstantMessage(Guid FromPeer, Guid FromPeerCheckpoint, Guid MessageUID, int MessageType,      byte[] Data) {

 

       if(MessageType == APP_COMMANDS.IM_ONLINE.getCode()){

//update your list or do query to check all again

}else....

}

 

 

We will talk about instant messages later so you could fully understand this code.

 

In state changed notification handler: 

public void onConnectionManager_PeerStateChanged(Peer peer, int State){...

 

you see two arguments Peer and State. First is complete fresh peer object of peer whose state changed and State is new state of that peer. You notice it is just int value so you are free to use any value in range from 2 to Integer.MAX_VALUE for your application.

 

Values 0 and 1 are reserved for

 

qp2plib.Peer.DisconnectedState    = 0;

qp2plib.Peer.AuthentificatedState = 1;

 

 

System depends on them so you cannot use these values for anything else.

 

To notify other peers about your state change code would be something like this:

 

- for example you are now busy

 

cm.getLocalPeer().setState(MyApplicationPeerStateEnums.BUSY.getCode());

cm.BrodcastStateChange();

 

So we use  cm.BrodcastStateChange() to inform others of our state changes. This function also updates our peer object information on super-node so we also use it when we change meta properties while session is active.

 

You don't need to do state broadcast when you are closing session/disposing C.M. because

ConnectionManager  will do that automatically.

 

 

5. Instant messages

 

 

Instant messages are carried using super-nodes, and their purpose is to transfer short messages between peers in network.  Often it is not practical to always open direct communication channel between two peers if one needs to inform other about something because tunnel creation takes 1-15s and also maybe we want first other peer to confirm that he willingly accepts that connection . These are commonly some control messages from your application or something like chat messages.

 

To be able to receive them you need to hook following event handler:

 

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onReceiveInstantMessage.getCode(), this);

...

public void onConnectionManager_ReceiveInstantMessage(Guid FromPeer, Guid FromPeerCheckpoint, Guid MessageUID, int MessageType,      byte[] Data) {

 

        if (MessageType == APP_COMMANDS.TextMessage.getCode()) {

//do something

}

else if (MessageType == APP_COMMANDS.IM_ONLINE.getCode()) {

//do something

}

else if (MessageType == APP_COMMANDS.ALLOW_FILE_TRANSFER.getCode()){

//do something

}...

}

 

 

To send instant message you use one of 4 overloads of cm.SendInstantMessage function:

 

public Guid SendInstantMessage(Guid RemotePeerCheckpointUID, Guid RemotePeerUID, byte[] message_buffer, int message_len, int InstantMessageType , iSendInstantMessageComplete_delegate callback_delegate)

      

public Guid SendInstantMessage(Guid RemotePeerCheckpointUID, Guid RemotePeerUID, String text_message, int InstantMessageType, iSendInstantMessageComplete_delegate callback_delegate

      

public Guid SendInstantMessage(Peer Peer, byte[] message_buffer, int message_len, int InstantMessageType, iSendInstantMessageComplete_delegate callback_delegate

      

public Guid SendInstantMessage(Peer Peer, String text_message, int InstantMessageType , iSendInstantMessageComplete_delegate callback_delegate)

 

Example of send instant message request:

 

cm.SendInstantMessage(OtherPeer,"Hi  man!", APP_COMMANDS.TextMessage.getCode());

 

You notice all have optional argument to InstantMessageType used to distinguish them, for example you could have number of control messages and text messages so this gives you easy way to distinguish them.

 

You also notice there is callback argument . That callback will trigger when message is delivered to your super-node, it does not mean it is delivered to other peer when callback triggers. Message will travel to destination peer super node and then it will be delivered to it.

 

 

6. Direct communication tunnel creation between peers

 

 

The main API feature and probably main reason why you use it is the ability to create direct communication channels between two computers that are both behind NAT (router) devices using NAT traversal techniques. The main advantage of this API is that as a result you will get standard platform socket to use from then on. System will self inspect what is the best method of tunnel criterion between two peers and create it. So you don't need to know anything  about STUN, NAT port mapping prediction, UPnP, NAT PMP, PCP... or anything else that happens in the background and just wait for your prepared socket.

 

(Note - special situations when both peers are on same local network or even on same computer are handled by API so data will be transferred locally in that case not going over the Internet ) .

 

In past texts we talked about how to find some peer. When you know that, and you want to open direct communication channel you initiate connect request. On the other side peer needs to accept this request so handshake procedure could start.

 

You need this two event handlers:

 

//When we make connect request (tunnel creation) we wait for operation completion to trigger event

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerConnected.getCode(), this);

 

//When some other peer initiate tunnel creation to us, operation completion will trigger event

cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerAccepted.getCode(), this);

...

//After successful connect operation this handler is triggered

public  void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) { {

//peer.getTunnelTCPSocket() or peer.getTunnelUDPSocket() is tunneled socket ready for data transfer

 

DataOutputStream out = new     DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());

out.writeUTF("Hello through tunnel!");

 

//do something else

}

.....

//THIS WILL TRIGGER WHEN OTHER SIDE CALL cm.connect(... TARGETING LOCAL PEER

void onPeerAcceptEventHandler(qp2plib::Peer *remotePeer,qp2plib::Guid * transactionUID){

{

//peer.getTunnelTCPSocket() or peer.getTunnelUDPSocket() is tunneled socket ready for data transfer 

DataInputStream in = new DataInputStream(peer.getTunnelTCPSocket().getInputStream());

DataOutputStream out = new DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());

 

byte[] buff = new byte [512];

int read = in.read(buff,0,512);

out.writeUTF("Hello through tunnel to you to!");

 

//do something else

}

 

One peer initiates connect request that would in this case use event handlers that look like this:

 

Guid track_connect_transaction_uid = cm.Connect(remotePeer,        ConnectionManager.ConnectionType.TCP.getCode(),null, null);

 

 

and on completion: 

 

public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID)

 

would trigger on his side. On other side:

 

public void onConnectionManager_PeerAccept(Peer peer, Guid TransactionUID)

 

would trigger on accepting connection.

 

if you need to control access - who can connect and who  cannot, you can override "Accept Connection

Reslover" by providing your resolver callback function:

 

cm.setAcceptPeerConnectionResolver(new ConnectionManager.iAcceptPeerConnection_delegate() {

       public boolean onConnectionManager_AcceptPeerConnection(

                           ConnectionManager sender, Peer peer, int connectionType_code) {

              if(<???some condition???>)

                     return true;

              else

                     return false;

       }

});

 

 

By default all connection request will be accepted unless  you set cm.setBlockIncoming( true);

 

Tunnel  accept/connect using callbacks would look like this:

 

 

SIDE THAT DOES CONNECT:

 

cm.Connect(remotePeer, ConnectionType.TCP, null,

    new ConnectionManager.iPeerConnectCallback_delegate() {

     public void onConnectionManager_PeerConnectCallback(Peer peer,

       Guid PeerCheckpointUID, Guid PeerUID, Guid TransactionUID,

       boolean Success, int PeerConnectFailureReason_code) {

         if(Success){

              DataOutputStream out = new DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());

             

              out.writeUTF("Hello through tunnel!");

              //...do something else...

         }else{

              Log.e("QuickP2P_API","Failed to connect, reason:" + ConnectionManager.PeerConnectFailureReason.values()[PeerConnectFailureReason_code].toString());

         }

       }

   }

);

 

 

SIDE THAT DOES ACCEPT:

 

cm.setPeerAcceptedCallback(

new ConnectionManager.iPeerConnectCallback_delegate() {

public void onConnectionManager_PeerConnectCallback(Peer peer,

              Guid PeerCheckpointUID, Guid PeerUID, Guid TransactionUID,

              boolean Success, int PeerConnectFailureReason_code) {

             

       if(Success){

              DataInputStream in =

                     new DataInputStream(peer.getTunnelTCPSocket().getInputStream());

              DataOutputStream out =

                     new DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());

 

              byte[] buff = new byte [512];

              int read = in.read(buff,0,512);

              out.writeUTF("Hello through tunnel to you to!");

       //...do something else...

       }else{

              Log.e("QuickP2P_API","Some peer tried to connect to us and failed, reason:" + ConnectionManager.PeerConnectFailureReason.values()[PeerConnectFailureReason_code].toString());

       }

 }

}

);

 

There is nothing special we need to talk about this, you have a ready socket so you do what you want with it from then on. Just have in mind that you are responsible for socket from then on so you need to close it when you no longer need it. Also as a good practice to follow is that you can just save that socket when you finish the task for new use if needed. So if you need do new task involving data transfer with that same

peer you can just pool out that socket instead of invoking tunnel creation again (1-15sec).  You can destroy such socket when you leave application or if that peer goes offline.

 

 

7. Using virtual index storage

 

 

Since quickP2P system goal is to provide you complete environment so you could focus just on application we introduced permanent index storage sub-system. We figured out that it would be convenient to provide way to store and be able to search some commonly used things like user registration data that is more closely aware of peers. You don't need to use this sub-system if you have your own in mind. You would just need some key you would store in peer properties to associate peers with your objects later.

 

You can imagine index system as remote database with two tables User and Network. Of course these are not ordinary tables that have fixed columns. You can store whatever property (name, value) you need and later search them. Besides common operations like SELECT/INSERT/UPDATE/DELETE what is most important is that User object is aware which peers are authenticated with it. Multiple peers can be authenticated with single user object and also open peer can be authenticated with multiple user objects. Both user and network object have UID and custom property bag. Constrains are that User object must have "Username" property and Network object needs to have "Name" property. There cannot be two User objects with same username in your application scope and same stands for name for networks. If you somehow need to enable different situation for some reason you can generate these properties in some special way based on some other property.

 

User object has columns "Member of networks" because you can join/un-join some user from network and "Currently bound Peers" which holds list of currently bound peer UIDs. User object also has password property you cannot see in provider portal.

 

Object used for virtual index management is accessed by cm.VNIndexManager(). All index operations complete synchronously because there are no critical time dependent functions . If you need asynchronous execution you need to implement it.  Also if there is something wrong exception is thrown.

 

This is list of all Virtual index manager operations:

 

public native VUser SaveUser(VUser user,String user_password);

 

- Inserts or updates virtual user. You pass user object you want to save and save it providing password for that user. If VUser UID property is 00000000-0000-0000-0000-000000000000 system will do INSERT otherwise it will do UPDATE. Result of operation is fresh User object like on index server in that moment this also

meant PeerUIDS property will have fresh list of authenticated peers. So if UID was empty UID will get some real value from server after operation completes.

This would be code to create new user:

 

VUser newUser = new VUser();

newUser.setUsername("some_username");//THIS IS REQUIRED, IT IS ALIAS FOR newUser ["username"]

newUser.set("name", "Mike");

newUser.set("email", "This email address is being protected from spambots. You need JavaScript enabled to view it.");

newUser = cm.VNIndexManager().SaveUser(newUser, "somepassword");

             

if (newUser.getUID().equals(Guid.Empty)) {

    System.out.println("User is saved , assigned UID is: " + ewUser.getUID().toString());

}

 

then update operation would be:

 

newUser.set("name", "Mike2");

newUser = cm.VNIndexManager().SaveUser(newUser, "somepassword");

 

Note that after this operation in case of creating new user getPeerUIDS()will be empty. Peer need to call in AuthenticateUser method in order to have his UID appearing in this list.

 

public VUser AuthenticateUser(String Username, String Password);

 

- Checks for existing username with password and if it exists adds peer UID to VUser getPeerUIDS() list , then returns VUser object:

 

qp2plib.VUser u;

...

u = cm.VNIndexManager().AuthenticateUser("some_username", "somepassword");

 

If you perform AuthenticateUser and user is already authenticated that is not bad operation. It will not have side effects and you can use it to get fresh VUser object from index server.

 

public VUser[] QueryUsers( String QueryCommandText, int skip, int limit , String ascOrderBy, String descOrderBy);

 

- Searches for VUsers matching query , adds list of found VUsers to provided result_list

cm.getVNIndexManager().QueryUsers(list_to_fill,"\"Properties.City\":\"Palo Alto\""); If you expect large number of objects to be returned consider using pagination. In last two optional parameters you enter comma separated property names .

 

public long QueryUsersCount(String QueryCommandText);

 

- Searches for VUsers matching query , then returns number of objects found

 

public void DeleteUser(Guid userToDeleteUID);

 

- Deletes VUser object having exact UID:

 

cm.getVNIndexManager.DeleteUser(u.getUID());

 

public void ChangeUserPassword(Guid userUID, String old_password, String new_password);

 

 

- Changes VUser object password. Passwords are not visible to anyone. In case that user forgets  password and wants to reset it, you need to use magic value:

(requesterPeerUID.ToString() + ApplicationUID.ToString() + userUID.ToString()).ToLower()

for old password to be able to reset it.

 

public void JoinUserToNetwork(Guid userUID , Guid networkUID);

 

- Updates NetworkUIDS list on index server in VUser object identified by userUID argument by adding provided networkUID. You need to update your local VUser object manually after that or to reload.

 

 

public void UnJoinUserFromNetwork(Guid userUID, Guid networkUID);

 

- Updates NetworkUIDS list on index server in VUser object identified by userUID argument by removing provided networkUID. You need to update your local VUser object manually after that or to reload it.

 

public VUser LogOffUser(String Username);

 

- Removes Peer UID from PeerUIDS list in VUser object on index server. Returns fresh copy of VUser

object.

 

public VNetwork SaveNetwork(VNetwork network);

 

- Inserts or updates (based on UID value) VNetwrok object. If UID is empty insert will be performed. Result is fresh copy of VNetwork object from index server.

 

qp2plib.VNetwork newNetwork = new qp2plib.VNetwork();

newNetwork.setName("network1");//THIS IS REQUIRED, IT IS ALIAS FOR newNetwork["name"]

newNetwork.set("owner","Mike");

newNetwork = cm.VNIndexManager().SaveNetwork(newNetwork);

if (newNetwork.getUID().equals(qp2plib.Guid.Empty))

{

       System.out.println("Network is saved , assigned UID is: " +                 newNetwork.getUID().toString());

}

 

then update operation would be:

 

newNetwork.set("owner","Jeff");

newNetwork = cm.VNIndexManager().SaveNetwork(newNetwork);

 

there cannot be to networks with same name in application scope.

 

public void DeleteNetwork(Guid networkToDeleteUID);

 

- Deletes VNetwork object having exact UID:

cm.VNIndexManager().DeleteNetwork(n.getUID());

 

public VNetwork[] QueryNetworks(String QueryCommandText, int skip, int limit , String ascOrderBy , String descOrderBy);

 

- Searches for VNetworks matching query , then returns list of found VNetworks to requester

cm.VNIndexManager().QueryNetworks(result_list,"\"Properties.Interes\":\"Ecology\""); If you expect large number of object to be returned consider using pagination. In last two optional parameters you enter comma separated property names .

 

public long QueryNetworksCount(String QueryCommandText);

 

- Searches for VNetworks matching query , then returns number of objects found

 

public VObject SaveObject(VObject obj);

 

- Inserts or updates (based on UID value) VObject object. If UID is empty insert will be performed. Result is fresh copy of VObject object from index server.

 

qp2plib.VObject newObject = new qp2plib.VObject();

newObject.set("something","[\"1\",\"2\",\"9\"]");

newObject = cm.VNIndexManager().SaveObject(newObject);

if (newObject.getUID().equals(qp2plib.Guid.Empty)){

       System.out.println("Object is saved , assigned UID is: " +           newObject.getUID().toString());

}

 

then update operation would be:

 

newObject.set("something","[\"1\",\"2\",\"9\",\"15\"]");

newObject = cm.VNIndexManager().SaveObject(newObject);

 

public void DeleteObject(Guid objectToDeleteUID);

 

- Deletes VObject object having exact UID:

cm.VNIndexManager().DeleteObject(newObject.getUID());

 

public VObject[] QueryObjects(String QueryCommandText, int skip, int limit , String ascOrderBy , String descOrderBy);

 

- Searches for VObject-s matching query , then returns list of found VObject-s to requester

cm.VNIndexManager().QueryObjects(result_list("\"Properties.something\":\"15\"");

If you expect large number of object to be returned consider using pagination. In last two optional

parameters you enter comma separated property names .

 

public native long QueryObjectsCount(String QueryCommandText);

 

Searches for VObjectss matching query , then returns number of objects found

 

Note we used json array for newObject.get("something")  value. Index server will automatically detect json array string shape or  json object string shape and store it in that form. It is important that you don't have blank characters before "[" or "{" in this cases because index server will not do object/array check in that case . This enables you full scale use of sub-objects in search queries.

 

You might need to have administrative application or site so you can give support to your user that can perform all index operation. When creating it have in mind that you must open session (become network peer) to gain access to index server.

 

8. Security, key exchange and data encryption/decryption

 

Quick P2P API provides you easy way to obtain secure keys to be known only to peers involved with tunnel. You use this keys to crypt/decrypt data during data transfer.

Secure key exchange is based on "DiffieHellman" algorithm with encampment to original concept. There is no "real" fixed key part, instead some short lived data existent for few seconds during handshake operation is used to generate what is called "Fixed part" in original concept. This eliminates possibility for attacker knowing fixed part to intrude data integrity using any encryption breaking theory.

32 bytes of security data is known only to two peers involved with tunnel. Security data is generated always during handshake procedure.  After each successful connect/accept , generated bytes can be obtained

from handler function Peer argument object :

 

public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) {

byte[] secret32Bytes = peer.getTunnelSecretData();

....

}

 

public void onConnectionManager_PeerAccept(Peer peer, Guid TransactionUID) {

byte[] secret32Bytes = peer.getTunnelSecretData();

....

}

 

quickP2P API has built-in classes that for AES encryption (chipering) . AES class - core AES encryption

If support following AES variants:

 

Key size:

·         128

·         192

·         256

 Chipering mode 

                     ECB (each block crypt with key separately, commonly used when partial decryption is required )

                      CBC (default - each block is XOR-ed with previous crypted block then crypted with key. First 16 chunk is XOR-ed using CBCInitialVector. Single byte difference makes whole data unusable )

 Padding:

                 None

                 PCKS7

 

AESNetworkStream - used to prepare data for crypt transfer when tunnel is stream based (TCP). AESNetworkStream is not standardized and its designed for easy use with quickP2P tunnels (you can use to else ware if both sides have it as first gate). AESNetworkStream enables you to use CBC mode for real time communication because provides you ability to use fairly sized blocks crypt with CBC chipering. Normally in CBC mode you would need to get all data (like whole file)  , then decrypt it so you could use it. AESNetworkStream can use more secure CBC mode and provide you partial data decryption.

For UDP datagrams you can simply use core AES class.

One thing to note when working with this classes is that original and crypted data do not have same size.

Crypted data is usually slightly larger than original.

Next is simple example of crypting and decrypting text with core AES class:

 

//we generate some random key just for test

byte[] key = qp2plib.Guid.NewGuid().getBytes();

qp2plib.AES a = new qp2plib.AES(qp2plib.AES.KeySize.Bits128 ,key, qp2plib.AES.Padding.PCKS7, qp2plib.AES.Mode.AESM_CBC);

//we generate some test text and convert it to bytes const char *original = "Hello I'am text to crypt"; int sLen = strlen(original);

byte[] buff1 = new byte[512];

byte[] original = "Text to crypt".getBytes("UTF-8");

//we crypt

int cryptlen = a.Crypt(original ,0,original.length,buff1,0);

byte[] buff2 = new byte[512];

//we decrypt

int dlen = a.Decrypt(buff1, 0, cryptlen, buff2, 0);

//we convert bytes to text buff2[dlen] = '\0';

String decrypted_text = new String(buff2,0,dlen);

 

This is same thing done with AESNetworkStream:

 

//we generate some random key qp2plib::AESNetworkStream ans( qp2plib::AES::AESKS_Bits128 , key);

byte[] key = qp2plib.Guid.NewGuid().getBytes();

//init AESNetworkStream with random key

qp2plib.AESNetworkStream ans = new qp2plib.AESNetworkStream(qp2plib.AES.KeySize.Bits128 , key);

//we generate some test text

byte[] original = "Hello I'am text to crypt".getBytes("UTF-8");

//we write data to stream to be crypted ans.WriteForCrypt((unsigned char*)original,0,slen); unsigned char buff1[512];

//we read crypted data form stream

int len = ans.ReadCrypted(buff1,0,8192);

//we now write crypted data for decryption ans.WriteForDeCrypt(buff1,0,len);

byte[] buff2 = new byte[512];

//we read decrypted data

len = ans.ReadDeCrypted(buff2, 0, ans.AvailableDeCrypted());

//we convert bytes back to text buff2[len] = '\0';

String decrypted_text = new String(buff2,0,len);

 

Usually you also need to set CBC initial vector (commonly "IV" ) if you are using CBC mode. Quick P2P AES class will  generate it if not provided based on key value. In above example we didn't set it but it's recommended you do so. Both classes have means to provide you pointer to internal CBC internal vector buffer you can

set bytes before you do any crypt/decrypt operation.

 

9. Required app permissions

 

When using quickP2P java API on desktop operation systems It's recommended that you add firewall exception  in local OS for your application. Communication will find its paths anyway but firewall exception can drastically increase performance. While developing you can do that manually but you would probably need a way to do that on user computers. This is generally most important for Windows operating systems because for other system this API version is used mostly this is defined in app bundle itself or system ask once user to allow this behavior so there is no need to do anything.

Only permission required by android applications for quickP2P for functioning is:

                android.permission.INTERNET

 

10. Complete list of delegates interfaces used in ConnectionManager with their in-prints

 

*Delegate interface for function deciding of peer acceptance. By default only

BlockIncoming parameter is considered

ConnectionManager - Connection manager

Peer - Peer requesting connection

ConnectionType - Requested connection protocol type

Allow - Output parameter as result of operation: true - alow connection , false - disallow connection

public interface iAcceptPeerConnection_delegate{

    boolean onConnectionManager_AcceptPeerConnection(ConnectionManager sender, Peer peer,int connectionType_code);

}

 

 

*Status change delegate

sender - Sending service

Status - Current status

public interface iStatusChange_delegate{

    void onConnectionManager_StatusChange(ConnectionManager sender, int ConnectionManagerStatus_code);

}

 

 

*Session open with success Delegate interface

ConnectionManager - ConnectionManager that opened session

public interface iSessionOpenSuccess_delegate{

   void onConnectionManager_SessionOpenSuccess(ConnectionManager sender); 

}

 

 

*Session open failure Delegate interface

ConnectionManager - ConnectionManager that tried opening session

Reason - Reason

public interface iSessionOpenFailure_delegate{

   void onConnectionManager_SessionOpenFailure(ConnectionManager sender,int SessionFailReason_code);  

}

 

 

*Session open callback Delegate interface

ConnectionManager - Callback to use with ConnetionManager.Open function to be execute on operation completion

Success - Outcome of session opening process

Reason - If Success false , gives reason for that outcome

public interface iSessionOpenCallback_delegate{

       void onConnectionManager_SessionOpenCallback(ConnectionManager sender,boolean success,int SessionFailReason_code);

}

 

 

*Session drop callback Delegate interface

ConnectionManager - Connection manager witch session droped

public interface iSessionDrop_delegate{

       void onConnectionManager_SessionDrop(ConnectionManager sender);

}

 

 

*Delegate interface for function to trigger when connection to peer (if connect) is successfully created

Peer - Connected peer

TransactionUID - Unique identifier on connect operation

public interface iPeerConnect_delegate{

       void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID);

}

 

 

*Delegate interface for function to trigger when connection to peer (if accept) is successfully created

Peer - Accepted peer

TransactionUID - Unique identifier on connect operation

    public interface iPeerAccept_delegate{

       void onConnectionManager_PeerAccept(Peer peer, Guid TransactionUID);

    }

  

   

*Delegate interface to function to be called on results arival after peer query request

FoundPeers - List of peers returned by query

Count - Number of peers returned by query

Completed - If completed then true, not that sometimes result will contain peers but this value will be false. This can happen for example if 10000000 peers are found by serch criteria , and operation timeout expires before all results are collected

QueryTransactionID - Transaction ID of query request

Page - Page returned if pagination exists

PageLimit - Number of peers per page if pagination exists

TotalPeers - Total number of peers matched by query

    public interface iPeerQueryCompleted_delegate{

       void onConnectionManager_PeerQueryCompleted(Peer[] peers,int count,boolean Completed, Guid QueryTransactionID,int Page,int PageLimit,int TotalPeers);

    }

  

   

*Delegate interface to function to be called to be called when information about peer state change arrives 

Peer - Peer that changed state

State - Value of the peer's state field

public interface iPeerStateChanged_delegate{

       void onConnectionManager_PeerStateChanged(Peer peer,int State);

}

 

 

*Delegate interface to pass as argument to ConnectionManager.Connect to be executed on connection creation

Peer - Peer object (remote peer) if success = true , otherwise null

PeerCheckpointUID - Unique identifier of remote peer's checkpoint

PeerUID - Unique identifier of remote peer

TransactionUID - Unique operation of connect operation

Success - Outcome of connect operation

FailReason - If success = false , then reason for failing

public interface iPeerConnectCallback_delegate{

       void onConnectionManager_PeerConnectCallback(Peer peer, Guid PeerCheckpointUID,Guid PeerUID, Guid TransactionUID, boolean Success,int PeerConnectFailureReason_code);

}

 

 

*Delegate interface to function to be called when instant message is Received

FromPeer - UID (Unique identifier) of remote peer that has sent instant message

FromPeerCheckpoint - UID (Unique identifier) of remote peer' checkpoint server that has sent instant message

 FromPeer - Unique identifier of remote peer

FromPeerCheckpoint - Unique identifier of remote peer's checkpoint

MessageUID - Unique identifier of message

MessageType - Integer value that can be used to distinguish instant messages by several different types if needed in application

Data - Received bytes 

public interface iReceiveInstantMessage_delegate{

       void onConnectionManager_ReceiveInstantMessage(Guid FromPeer,Guid FromPeerCheckpoint,Guid MessageUID,int MessageType,byte[] Data);       

}

 

 

*Delegate interface for function to be called when connection attempt fails

PeerCheckpointUID - Checkpoint UID of peer to whom connection creation was attempted

PeerUID - UID of peer to whom connection creation was attempted

TransactionUID - Transaction UID

Reason - Reason of failure

public interface iPeerConnectFailure_delegate{

       void onConnectionManager_PeerConnectFailure(Guid PeerCheckpointUID,Guid PeerUID,Guid TransactionUID,int PeerConnectFailureReason_code);

}

 

 

*Delegate interface for function to be called when connection acceptation attempt fails

PeerCheckpointUID - Checkpoint UID of peer from whom connection creation was attempted

PeerUID - UID of peer from whom connection creation was attempted

TransactionUID - Transaction UID

Reason - Reason of failure

public interface iPeerAcceptFailure_delegate{

       void onConnectionManager_PeerAcceptFailure(Guid PeerCheckpointUID,Guid PeerUID,Guid TransactionUID,int PeerConnectFailureReason_code);

}

 

 

*Delegate interface for function to be called when instant message is sent to checkpoint server

MessageGUID - UID of instant message

success - true if sending was successful, otherwise false

public interface iSendInstantMessageComplete_delegate{

       void onConnectionManager_SendInstantMessageComplete(Guid MessageGUID,boolean success);

}

 

 

*Delegate interface for function to be called when state track action (register/un-register tracking) fails

TransactionUID - UID of transaction

public interface iStateTrackActionFailed_delegate{

       void onConnectionManager_StateTrackActionFailed(Guid TransactionUID);

}

 

 

 

*Delegate interface for function to be called when ConnectionManager dispatcher network interface changes

ConnectionManager - ConnectionManager that raised event

IP4Address - New address dispatcher is bound to

public interface NetworkInterfaceChange_delegate{

       void onConnectionManager_NetworkInterfaceChange(ConnectionManager sender, InetSocketAddress newAddress);

}