quickP2P API (java/android SDKversion) -programming instructions
AUXILIARYMATERIAL:
- "qp2p_SimplestDemo_FileTransfer" demoeclipse android application project
quickP2P API (java/android SDKversion) -programming instructions
Startingwithjava/android SDK API(withandroid SDKsamples)
1. quickP2P lib delegate/eventsystem
1.1Events in quickP2PAPI
1.2 Callbacks in quickP2PAPI
1.3Event/Callback handler execution
2. Peer session creation
3. Peer lookup queries
4. Peer state tracing /changes notifications
5. Instantmessages
6. Direct communicationtunnelcreationbetweenpeers
7. Using virtualindexstorage
8. Security, keyexchangeand dataencryption/decryption
9. Required app permissions
10.Completelistofdelegates interfacesusedin ConnectionManagerwiththeirin-prints
1. quickP2P lib delegate/eventsystem
qP2P lib 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 a 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. The method needs to have the 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 some basic examples of using events/callbacks and there is no better way than giving simple examples. Once hooked event handler will be executed every time corresponding event triggers until we un-hook it manually or simply destroy objects. On the contrary, callback is usually passed as some method argument. It will execute only once to handle particular method execution asynchronous response.
1.1Events in quickP2PAPI
- 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:
publicclass TestActivity extends Activity implements
qp2plib.ConnectionManager.iPeerConnect_delegate {
private qp2plib.ConnectionManager cm = null;
…
@Override
protectedvoid 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
publicvoid onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) throws IOException {
//... handler body.
}
…
}
Here is how we would hook the same event to anonymous object method:
publicclass TestActivity extends Activity {
private qp2plib.ConnectionManager cm = null;
…
@Override
protectedvoid 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(){
publicvoid onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) throws IOException {
//... handler body.
}
});
…
}
…
}
Reference to delegate object will be kept in inner event/callback object. That will prevent the garbage collector from destroying an anonymous object that serves as a delegate.
1.2 Callbacksin quickP2PAPI
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 the same particular event, both event handler and callback handler will be executed, first event handler then callback handler.
Now here is a use of callbacks demonstration on cm.Connect method. Callback delegate is the last argument of ConnectionManager.connect method. This callback executes when a nat-traversal operation completes. Here is callback handler in an anonymous object:
cm.Connect(remotePeer, ConnectionType.TCP.getCode() , null, new
ConnectionManager.iPeerConnectCallback_delegate(){
publicvoid 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. That will prevent the garbage collector from destroying an anonymous object that serves as a delegate.
And here would be an example using calling class as a delegate with the appropriate method defined in it:
cm.Connect(remotePeer, ConnectionType.TCP.getCode() , null, this);
….
//this is implemented method qp2plib.ConnectionManager.iPeerConnect_delegate
publicvoid 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. The 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. That thread will probably not be UI thread. Watch out when interacting with UI elements you need to transfer UI directives to UI thread. This is a rule that stands for all platforms/languages and not just for Java. Consider following example:
publicvoid onConnectionManager_SessionOpenCallback(ConnectionManager sender,boolean success,int SessionFailReason_code){
finalint code_val = SessionFailReason_code;
runOnUiThread(
new Runnable() {
privateintcode = code_val;
publicvoid 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 the same thread and do tasks. API will create other new threads if needed for events that happen while that long-running task is being executed.
Also, there is conservation mechanism, because thread creation in java can be expensive. After the 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 from 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
publicvoid 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 = newbyte[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(newbyte[] {(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 through tunneled TCP socket right in handler body. This is the ok place because we know this will not block API and that other events happening to meanwhile will be served by other threads API 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 (a function that looks like this:
protectedvoid 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() {
publicvoid run() {
ShowMessage(msg);
}
}
);
}
protectedvoid ShowMessage(String message){
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
We want to show a message box to the user, and a 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 an instant message, searching peers, virtual index operation ... we first need to join an abstract peer-to-peer network like you connect to the internet when your computer powers up. Steps for joining application peer to the super-node network would be this:
1- CreateConnectionManager object to handle operations, set required C.M. properties, handles...
It's always requiredtosetaccesstokens:
cm.setAccessTokens("00000000-XXXX-XXXX-XXXX-XXXXXXXXXXXX","00000000-XXXX-XXXX-XXXX- XXXXXXXXXXXX","");
The 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","blablabla");
cm.getLocalPeer().set("email", "someone@somepeople.com");
cm.getLocalPeer().set("gender","male");
you can also alter these properties later while the session is opened, you need to call
cm.BrodcastStateChange()toupdatelocalpeerinformationonnetwork.
3 - InitateConnectionManager.Open(callback) toopensession
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 a session with super-node we can:
- Query for other peers on the network (Peer lookup) with mongo DB-like queries in our application scope
- Register for peers’ status tracking and receive notifications about changes
- Send/Receiveinstantmessages
- Open communication tunnels to other peers and other peers can open tunnels to us
- Use virtual index manager if we need it:
- Create|Delete|EditVirtualNetworks|Users
- Search virtual networks|users with mongo DB like queries
- Join|Un-joinusers tonetworks
....
We will talk about all these operations in following texts
Peer lookup queries query for peers that are currently online. A 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:
publicinterface iPeerQueryCompleted_delegate{
void onConnectionManager_PeerQueryCompleted(Peer[] peers,int count,boolean Completed, Guid QueryTransactionID,int Page,int PageLimit,int TotalPeers);
}
Agruments:
FoundPeersListofpeersreturnedbyquery
CountNumberofpeersreturnedbyquery
CompletedIfcompletedthentrue,notthatsometimesresultwillcontainpeersbutthis valuewillbe false.Thiscanhappenforexampleif10000000peersarefoundbysearch criteria, and operationtimeoutexpiresbeforeallresultsarecollected
QueryTransactionIDTransactionIDofqueryrequest
PagePagereturnedifpaginationexists
PageLimitNumberofpeersperpageifpaginationexists
TotalPeersTotalnumberofpeersmatchedbyquery
Eventhookexample:
...
cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerQueryResponse.getCode(), this);
...
publicvoid onConnectionManager_PeerQueryCompleted(Peer[] peers, int count,
boolean Completed, Guid QueryTransactionID, int Page,
int PageLimit, int TotalPeers) {....
Query text format is mongo DB like a 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","PaloAlto");
cm.getLocalPeer().set("Sex","female");
cm.getLocalPeer().set("Email","peer@peers.com");
cm.getLocalPeer().set("Age","31");
Here are some examples of queries that will include this peer in result:
Findpeerwithemail"peer@peers.com"thatiscurrentlyonline:
cm.Query("\"Properties.Email\":\"peer@peers.com\"");
Findpeerswithemailcontainedinthislist[peer@peers.com,mike@peers.com,a123@bla.com]thatarecurrently online:
cm.Query("\"Properties.Email\":{$in:[\"peer@peers.com\",\"mike@peers.com\",\"a123@bla.com\"]}");
FindfemalepeersfromPaloAltothatarecurrentlyonline:
cm.Query("\"Properties.City\":\"PaloAlto\",\"Properties.Sex\":\"female\"");
FindfemalepeersfromPaloAltowithabove30thatarecurrentlyonline:
cm.Query("\"Properties.City\":\"PaloAlto\",\"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
Thereisoften aneedfor somepeertohavesomestate onnetwork, likeaway, busy, available .... You probablyseen thatascommonfeatureof chat applications. Ifyouthinkyoucouldachievesame functionalityusingInstantmessagesor somethingelseyouarerightbutthis providesyoumoreelegant wayoftransferringsuchinformationtopeersinstantly. Alsotherearecertainsituationswhen thiscomes handyasirreplaceablefeature. Imagine youhavesomechatlikeapplicationandyoukeepyour peer buddies in somelistor dictionary...your buddykid rips powercablesfromhis computersohis application
don'tmanagetonotify youthatheisoffline. Super-nodewillnoticethatin max90sec,andifyouregistered for thatpeerstatetrackingyouwillgetnotificationthatheisoffline.
Tousethis featureyouneedtohookeventtoacceptnotifications:
cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerStateChanged.getCode(), this);
...
publicvoid onConnectionManager_PeerStateChanged(Peer peer, int State) {
if(State==0){
//0-isreservedfordisconnectedstate,1-initialconnected state,allvalues>1areavailablefor freeuse
ActiveFriends.erase(peer.getUID());//Peergotdisconnectedweremoveitfromour internallist
}
}
This is rarecasewhereyoudon'thave optiontousecallbackbecauseyouneverknowwhen notification willcome.
Alsoyouhavetoinformsuper-nodesthatyouwanttobenotified aboutthatpeer changes.Youdothat 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);
CommonplacetoplaceRegisterStateTrackingmethodisin abodyof peer query responsehandlerbecausethatis momentwhenyougetfresh informationofwhoisonline. If peer goesoffline youwill get qp2plib.Peer.DisconnectedState (=0)stateargumentvalue. If he comesbackhe needs toinform youthatheis back onlineand youagainneedtoregisterfor itsstatetrackingagain.So statustrackisvaliduntilpeergoesoffline, if he comes backyouneedtoregisterstatetrackingagain.Also youseethereisa needtocombineinstant messageswith thistomakeitfullyusable ,why:
- You could dopeerqueryand you wouldgetfresh informationabout onlinepeers, butafter30secsomepeerthat shouldbe visibleto youby particular applicationimplementation appearsonnetwork. Youdon'tknowthatheisonlineinthat moment andyouwouldnotknowthatuntil youdopeerqueryagain. Buthealsodoespeer queryandheknows thatyouareonlineso allhehas todois to informyouaboutthat. Hecando thatbysimply sendingyousomeinstantmessage.
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.
publicvoid onConnectionManager_ReceiveInstantMessage(Guid FromPeer, Guid FromPeerCheckpoint, Guid MessageUID, int MessageType, byte[] Data) {
if(MessageType==APP_COMMANDS.IM_ONLINE.getCode()){
//updateyour list or doquery tocheckallagain
}else....
}
Wewilltalkaboutinstantmessageslater soyoucouldfullyunderstandthiscode.
Instatechanged notificationhandler:
publicvoid onConnectionManager_PeerStateChanged(Peer peer, int State){...
youseetwoargumentsPeerandState. Firstis completefreshpeerobjectofpeerwhosestatechanged and Stateisnewstateof thatpeer.Younoticeit is just intvaluesoyouarefreetouseanyvalueinrange from2toInteger.MAX_VALUEfor your application.
Values0and1arereservedfor
qp2plib.Peer.DisconnectedState = 0;
qp2plib.Peer.AuthentificatedState= 1;
Systemdepends onthemsoyoucannotusethesevaluesforanythingelse.
Tonotifyother peers aboutyour statechangecodewouldbesomethinglikethis:
- forexampleyouarenowbusy
cm.getLocalPeer().setState(MyApplicationPeerStateEnums.BUSY.getCode());
cm.BrodcastStateChange();
So weuse cm.BrodcastStateChange(); toinformothers ofourstatechanges.This functionalsoupdates our peerobjectinformationonsuper-nodesowealsouseitwhenwechangemetapropertieswhile sessionisactive.
Youdon'tneedtodostatebroadcastwhenyouareclosingsession/disposingC.M. because
ConnectionManager willdo thatautomatically.
5. Instant messages
Instant messages arecarriedusingsuper-nodes,andtheir purposeisto transfershortmessagesbetween peers innetwork. Often itis notpracticaltoalwaysopen directcommunicationchannel betweentwo peers ifoneneedstoinformotheraboutsomethingbecausetunnelcreationtakes1-15s andalso maybe wewant firstother peertoconfirm thathewillinglyacceptsthat connection.Thesearecommonlysome controlmessagesfromyour applicationor somethinglikechatmessages.
Tobeabletoreceivethemyouneed tohookfollowingevent handler:
cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onReceiveInstantMessage.getCode(), this);
...
publicvoid onConnectionManager_ReceiveInstantMessage(Guid FromPeer, Guid FromPeerCheckpoint, Guid MessageUID, int MessageType, byte[] Data) {
if(MessageType ==APP_COMMANDS.TextMessage.getCode()) {
//dosomething
}
elseif (MessageType== APP_COMMANDS.IM_ONLINE.getCode()){
//dosomething
}
elseif (MessageType== APP_COMMANDS.ALLOW_FILE_TRANSFER.getCode()){
//dosomething
}...
}
Tosend instantmessageyouuseoneof4 overloadsofcm.SendInstantMessagefunction:
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)
Exampleof sendinstantmessage request:
cm.SendInstantMessage(OtherPeer,"Hi man!", APP_COMMANDS.TextMessage.getCode());
Younoticeall haveoptional argument toInstantMessageTypeused todistinguishthem, for exampleyou could havenumberof controlmessagesandtextmessages so this givesyoueasywaytodistinguishthem.
Youalsonoticethereiscallbackargument .That callbackwilltriggerwhenmessageis delivered toyour super-node,itdoesnotmeanitis delivered toother peerwhen callbacktriggers.Messagewilltravel to destinationpeersupernodeandthen itwillbedelivered to it.
6. Direct communication tunnel creation between peers
ThemainAPIfeatureandprobablymainreasonwhy youuseitistheabilitytocreatedirectcommunication channels betweentwocomputersthat areboth behindNAT(router)devicesusingNATtraversal techniques.Themainadvantage ofthis API isthat asaresultyouwill getstandardplatformsockettouse fromthenon.Systemwillself inspectwhatisthebestmethodof tunnelcriterionbetween two peersand createit. Soyoudon'tneedtoknowanything aboutSTUN, NATportmappingprediction,UPnP, NATPMP, PCP...or anythingelsethathappens in thebackgroundand justwaitforyour prepared socket.
(Note - specialsituations when both peersareonsamelocal networkor evenonsame computerare handled byAPIsodatawillbetransferred locallyin thatcasenotgoing overtheInternet).
Inpast textswetalkedabouthow tofindsomepeer.When youknowthat,andyouwantto open direct communicationchannel youinitiateconnectrequest. On theotherside peer needs toacceptthis request sohandshakeprocedurecouldstart.
Youneed thistwoeventhandlers:
//Whenwemakeconnectrequest(tunnelcreation)wewaitforoperationcompletiontotrigger event
cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerConnected.getCode(), this);
//Whensomeotherpeer initiatetunnelcreationtous,operationcompletionwilltrigger event
cm.addEventHandler(ConnectionManager.ConnectionManagerEvent.onPeerAccepted.getCode(), this);
...
//Aftersuccessfulconnectoperationthishandleristriggered
public void onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) { {
//peer.getTunnelTCPSocket() or peer.getTunnelUDPSocket() istunneledsocketreadyfordatatransfer
DataOutputStream out = new DataOutputStream(peer.getTunnelTCPSocket().getOutputStream());
out.writeUTF("Hellothroughtunnel!");
//dosomethingelse
}
.....
//THISWILLTRIGGERWHENOTHERSIDECALL cm.connect(...TARGETINGLOCALPEER
voidonPeerAcceptEventHandler(qp2plib::Peer*remotePeer,qp2plib::Guid*transactionUID){
{
//peer.getTunnelTCPSocket() or peer.getTunnelUDPSocket() istunneledsocketreadyfordatatransfer
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("Hellothroughtunneltoyouto!");
//dosomethingelse
}
One peerinitiatesconnectrequestthatwouldinthiscaseuseeventhandlersthatlooklikethis:
Guid track_connect_transaction_uid =cm.Connect(remotePeer, ConnectionManager.ConnectionType.TCP.getCode(),null, null);
andoncompletion:
publicvoid onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID)
wouldtrigger onhis side. Onother side:
publicvoid onConnectionManager_PeerAccept(Peer peer, Guid TransactionUID)
would triggeronacceptingconnection.
if youneedtocontrolaccess- whocanconnectandwho cannot,youcanoverride "AcceptConnection
Reslover" byproviding your resolver callback function:
cm.setAcceptPeerConnectionResolver(new ConnectionManager.iAcceptPeerConnection_delegate() {
publicboolean onConnectionManager_AcceptPeerConnection(
ConnectionManager sender, Peer peer, int connectionType_code) {
if(<???some condition???>)
returntrue;
else
returnfalse;
}
});
Bydefaultall connectionrequest will beacceptedunless yousetcm.setBlockIncoming(true);
Tunnel accept/connectusingcallbackswouldlooklikethis:
SIDETHAT DOESCONNECT:
cm.Connect(remotePeer, ConnectionType.TCP, null,
new ConnectionManager.iPeerConnectCallback_delegate() {
publicvoid 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());
}
}
}
);
SIDETHAT DOESACCEPT:
cm.setPeerAcceptedCallback(
new ConnectionManager.iPeerConnectCallback_delegate() {
publicvoid 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 = newbyte [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());
}
}
}
);
Thereisnothingspecialweneedtotalkaboutthis, youhaveareadysocketsoyoudo whatyouwantwith it fromthenon. Justhaveinmindthatyouareresponsible forsocketfromthenonsoyouneed tocloseit when younolongerneedit. Alsoasagoodpracticetofollowisthatyoucanjust savethat socketwhenyou finishthetaskfor newuse if needed.Soifyouneeddonewtaskinvolvingdatatransferwiththatsame
peer youcanjustpooloutthat socketinsteadof invokingtunnel creationagain(1-15sec). Youcandestroy suchsocketwhenyouleaveapplicationorif thatpeergoesoffline.
7. Using virtual index storage
Since quickP2Psystemgoal istoprovideyoucompleteenvironmentso youcouldfocus justonapplication we introduced permanent indexstoragesub-system. Wefigured outthat it wouldbeconvenient toprovide waytostoreandbeabletosearchsomecommonlyused things likeuser registrationdatathat is more closelyawareof peers.Youdon'tneed tousethis sub-systemif youhaveyourownin mind.You wouldjust needsomekey you wouldstoreinpeer properties toassociatepeerswithyourobjectslater.
Youcanimagineindexsystemasremotedatabasewith two tablesUserandNetwork.Of coursetheseare notordinarytables thathavefixedcolumns.Youcanstorewhateverproperty(name,value)youneedand later search them.BesidescommonoperationslikeSELECT/INSERT/UPDATE/DELETEwhatismost importantisthatUserobjectis awarewhichpeersareauthenticated with it.Multiple peers canbe authenticated withsingle userobjectandalsoopen peer canbeauthenticated with multipleuserobjects. Both userandnetworkobjecthaveUIDandcustompropertybag. Constrains arethatUserobjectmust have"Username"propertyandNetworkobjectneedstohave"Name"property.Therecannotbetwo User objects with sameusernamein your applicationscopeandsamestands for namefor networks. Ifyou somehowneed toenable differentsituationforsomereasonyoucangeneratethesepropertiesinsome special waybased onsomeotherproperty.
Userobjecthas columns "Memberof networks"becauseyoucanjoin/un-joinsomeuserfromnetwork and "Currentlybound Peers"whichholds listofcurrently boundpeerUIDs. Userobjectalsohaspassword propertyyoucannotseeinproviderportal.
Objectused for virtualindex management is accessed by cm.VNIndexManager().All index operations complete synchronouslybecausethereare nocriticaltimedependent functions . If youneedasynchronousexecution youneed toimplementit. Alsoifthereis somethingwrongexceptionisthrown.
This is listofall Virtual index manageroperations:
publicnative VUser SaveUser(VUser user,String user_password);
- Insertsor updates virtualuser. Youpass user objectyouwantto saveandsaveit providingpasswordfor that user.IfVUser UIDpropertyis00000000-0000-0000-0000-000000000000systemwill doINSERTotherwiseit willdoUPDATE.Resultofoperationisfresh Userobjectlikeonindexserverinthatmomentthis also
meant PeerUIDSproperty willhavefreshlistofauthenticatedpeers. SoifUIDwas empty UIDwillgetsome real valuefromserverafteroperationcompletes.
This wouldbecodetocreatenewuser:
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 updateoperationwouldbe:
newUser.set("name", "Mike2");
newUser = cm.VNIndexManager().SaveUser(newUser, "somepassword");
Notethat afterthis operationin caseof creatingnew user getPeerUIDS()will beempty.Peer needto call inAuthenticateUsermethodinorder tohave hisUIDappearingin this list.
public VUser AuthenticateUser(String Username, String Password);
- Checksforexistingusernamewith passwordandifitexistsaddspeerUIDtoVUser getPeerUIDS()list, then returns VUserobject:
qp2plib.VUser u;
...
u = cm.VNIndexManager().AuthenticateUser("some_username", "somepassword");
If youperformAuthenticateUseranduser isalreadyauthenticated thatis notbadoperation. Itwill not havesideeffectsand youcanuseittogetfreshVUser objectfromindex server.
public VUser[] QueryUsers( String QueryCommandText, int skip, int limit , String ascOrderBy, String descOrderBy);
- SearchesforVUsersmatchingquery,addslistof found VUsers toprovidedresult_list
cm.getVNIndexManager().QueryUsers(list_to_fill,"\"Properties.City\":\"PaloAlto\""); If youexpectlargenumberofobjectstobereturned considerusingpagination. Inlasttwo optional parameters youentercomma separatedpropertynames.
publiclong QueryUsersCount(String QueryCommandText);
- SearchesforVUsersmatchingquery,then returnsnumberofobjectsfound
public void DeleteUser(Guid userToDeleteUID);
- DeletesVUserobjecthavingexactUID:
cm.getVNIndexManager.DeleteUser(u.getUID());
publicvoid ChangeUserPassword(Guid userUID, String old_password, String new_password);
- ChangesVUserobjectpassword.Passwordsarenotvisible toanyone. Incasethat user forgets password andwantstoresetit,youneedtousemagicvalue:
(requesterPeerUID.ToString()+ ApplicationUID.ToString()+userUID.ToString()).ToLower()
foroldpasswordtobeabletoresetit.
publicvoid JoinUserToNetwork(Guid userUID , Guid networkUID);
- UpdatesNetworkUIDSlistonindexserverinVUserobjectidentified byuserUIDargument byadding providednetworkUID. Youneedtoupdate your localVUserobjectmanuallyafter thator toreload.
publicvoid UnJoinUserFromNetwork(Guid userUID, Guid networkUID);
- UpdatesNetworkUIDSlistonindexserverinVUserobjectidentified byuserUIDargument byremoving providednetworkUID. Youneedtoupdate your localVUserobjectmanuallyafter thator toreloadit.
public VUser LogOffUser(String Username);
- RemovesPeerUIDfromPeerUIDSlistin VUserobject onindexserver. Returnsfresh copyofVUser
object.
public VNetwork SaveNetwork(VNetwork network);
- Insertsor updates (basedonUIDvalue)VNetwrokobject. IfUIDisemptyinsertwillbeperformed.Result is fresh copyofVNetworkobjectfromindexserver.
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 updateoperationwouldbe:
newNetwork.set("owner","Jeff");
newNetwork = cm.VNIndexManager().SaveNetwork(newNetwork);
therecannotbetonetworkswithsamenamein applicationscope.
publicvoid DeleteNetwork(Guid networkToDeleteUID);
- Deletes VNetworkobjecthavingexactUID:
cm.VNIndexManager().DeleteNetwork(n.getUID());
public VNetwork[] QueryNetworks(String QueryCommandText, int skip, int limit , String ascOrderBy , String descOrderBy);
- SearchesforVNetworksmatchingquery,then returns listof foundVNetworkstorequester
cm.VNIndexManager().QueryNetworks(result_list,"\"Properties.Interes\":\"Ecology\""); If youexpectlargenumberofobject tobereturnedconsiderusingpagination. Inlasttwo optional parameters youentercomma separatedpropertynames.
publiclong QueryNetworksCount(String QueryCommandText);
- SearchesforVNetworksmatchingquery,then returns numberofobjectsfound
public VObject SaveObject(VObject obj);
- Insertsor updates (basedonUIDvalue)VObjectobject.If UIDisemptyinsertwill be performed.Resultis fresh copyofVObjectobjectfromindexserver.
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 updateoperationwouldbe:
newObject.set("something","[\"1\",\"2\",\"9\",\"15\"]");
newObject = cm.VNIndexManager().SaveObject(newObject);
publicvoid DeleteObject(Guid objectToDeleteUID);
- Deletes VObjectobjecthavingexactUID:
cm.VNIndexManager().DeleteObject(newObject.getUID());
public VObject[] QueryObjects(String QueryCommandText, int skip, int limit , String ascOrderBy , String descOrderBy);
- SearchesforVObject-smatchingquery,then returns listof foundVObject-storequester
cm.VNIndexManager().QueryObjects(result_list("\"Properties.something\":\"15\"");
If youexpectlargenumberofobject tobereturnedconsiderusingpagination. Inlasttwo optional
parameters youentercomma separatedpropertynames.
publicnativelong QueryObjectsCount(String QueryCommandText);
SearchesforVObjectssmatchingquery,then returns numberofobjectsfound
Notewe used json arrayfornewObject.get("something") value.Indexserver willautomaticallydetectjson arraystring shapeor jsonobjectstring shapeand storeitinthatform. Itisimportantthat you don't have blankcharactersbefore"["or"{"inthiscasesbecauseindexserverwill notdoobject/array checkin that case.This enables you fullscaleuseofsub-objectsinsearch queries.
Youmightneed tohaveadministrativeapplicationorsitesoyoucangivesupporttoyour user thatcan performall indexoperation. When creatingithaveinmindthat youmustopen session(becomenetwork peer) togainaccess toindexserver.
8. Security, key exchange and data encryption/decryption
Quick P2PAPI provides youeasyway to obtainsecurekeystobeknownonlytopeers involvedwith tunnel.You usethis keystocrypt/decryptdataduringdatatransfer.
Securekeyexchangeisbased on"Diffie–Hellman"algorithm with encampment tooriginal concept. There is no"real"fixedkeypart, insteadsomeshortlived dataexistentforfewsecondsduringhandshake operationisused togenerate whatiscalled "Fixed part"inoriginal concept.Thiseliminatespossibilityfor attackerknowingfixedparttointrudedata integrityusinganyencryptionbreakingtheory.
32bytes ofsecuritydataisknownonly totwo peersinvolved withtunnel. Securitydata isgeneratedalways duringhandshakeprocedure. After each successful connect/accept ,generatedbytescanbeobtained
from handler functionPeerargument object :
publicvoid onConnectionManager_PeerConnect(Peer peer, Guid TransactionUID) {
byte[] secret32Bytes = peer.getTunnelSecretData();
....
}
publicvoid onConnectionManager_PeerAccept(Peer peer, Guid TransactionUID) {
byte[] secret32Bytes = peer.getTunnelSecretData();
....
}
quickP2PAPIhas built-inclasses thatfor AESencryption(chipering). AESclass-coreAESencryption
If supportfollowingAESvariants:
Key size:
· 128
· 192
· 256
Chiperingmode
ECB (each blockcryptwithkeyseparately, commonly used when partial decryptionisrequired )
CBC (default- eachblockisXOR-ed with previous cryptedblockthen cryptedwithkey. First16 chunkis XOR-ed usingCBCInitialVector. Single bytedifferencemakeswhole dataunusable )
Padding:
None
PCKS7
AESNetworkStream- usedtopreparedataforcrypttransfer whentunnel isstreambased(TCP). AESNetworkStreamisnotstandardized anditsdesigned foreasyusewithquickP2Ptunnels (youcanuseto else wareifbothsideshaveitas firstgate).AESNetworkStreamenables youtouse CBC modefor real time communicationbecauseprovides youabilitytousefairly sized blockscryptwithCBC chipering. Normallyin CBC modeyou wouldneedtogetall data(likewholefile) ,then decryptitsoyoucoulduseit. AESNetworkStreamcanusemoresecureCBCmode andprovideyoupartial datadecryption.
For UDPdatagramsyoucansimplyusecoreAESclass.
One thingtonotewhenworkingwith thisclassesisthat original andcrypted data donothavesamesize.
Crypteddatais usuallyslightlylargerthan original.
Nextissimpleexample ofcryptinganddecryptingtextwith coreAES 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 = newbyte[512];
byte[] original = "Text to crypt".getBytes("UTF-8");
//we crypt
int cryptlen = a.Crypt(original ,0,original.length,buff1,0);
byte[] buff2 = newbyte[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 samethingdonewithAESNetworkStream:
//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 = newbyte[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);
UsuallyyoualsoneedtosetCBC initialvector (commonly"IV" )ifyouareusingCBC mode.Quick P2PAES class will generateitif notprovided basedonkey value. Inaboveexamplewedidn'tsetitbutit's recommended youdoso. Both classeshavemeans toprovideyoupointer tointernal CBC internal vectorbufferyoucan
setbytesbeforeyoudoanycrypt/decryptoperation.
9. Required app permissions
When using quickP2P java API on desktop operation systems It's recommended thatyouaddfirewallexception inlocalOSforyour application. Communicationwillfind itspathsanywaybutfirewall exceptioncandrastically increaseperformance.While developing youcan do thatmanuallybutyouwouldprobablyneed awaytodothatonusercomputers.Thisisgenerally most important for Windowsoperatingsystems becausefor other systemthis APIversionisusedmostly thisis defined inappbundle itselfor systemaskonceuser toallowthis behavior so thereis noneedtodo 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
publicinterface iAcceptPeerConnection_delegate{
boolean onConnectionManager_AcceptPeerConnection(ConnectionManager sender, Peer peer,int connectionType_code);
}
*Status change delegate
sender - Sending service
Status - Current status
publicinterface iStatusChange_delegate{
void onConnectionManager_StatusChange(ConnectionManager sender, int ConnectionManagerStatus_code);
}
*Session open with success Delegate interface
ConnectionManager - ConnectionManager that opened session
publicinterface iSessionOpenSuccess_delegate{
void onConnectionManager_SessionOpenSuccess(ConnectionManager sender);
}
*Session open failure Delegate interface
ConnectionManager - ConnectionManager that tried opening session
Reason - Reason
publicinterface 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
publicinterface iSessionOpenCallback_delegate{
void onConnectionManager_SessionOpenCallback(ConnectionManager sender,boolean success,int SessionFailReason_code);
}
*Session drop callback Delegate interface
ConnectionManager - Connection manager witch session droped
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface 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
publicinterface NetworkInterfaceChange_delegate{
void onConnectionManager_NetworkInterfaceChange(ConnectionManager sender, InetSocketAddress newAddress);
}