/* This is a test console app for udp net interface.
 */
int main( int argc, char** argv )
{
	bool quit = false;
	PacketReader reader;
	PacketWriter writer;
	char buff[512];
	NetSession session;
	LocalClient server;
	LocalClient sender;
	int frame = 0;

	// Init
	session.OpenPort( 5000 );
	server = session.CreateLocalClient();

	//session.SetPacketLoss( 50 );


	// Send stuff
	ConsolePrintf( "Server is running.\n" );
	while ( !quit )
	{
		session.OnUpdate();
		while ( server.IsDataReady() )
		{
			server.ReceiveData( reader, sender );

			frame = reader.Read< int >();
			ConsolePrintf( "RECV: %d\n", frame );
			//quit = !strcmp( buff, "quit" );

			sprintf( buff, "echo: %d", frame );
			//ConsolePrintf( "Reply: %s\n", buff );
			writer.WriteString( buff );
			server.SendData( writer );
		}
	}


	// Quit
	

	return 0;
}
Example #2
0
void RTMFP::Encode(AESEngine& aesEncrypt,PacketWriter& packet) {
	if(aesEncrypt.type != AESEngine::EMPTY) {
		// paddingBytesLength=(0xffffffff-plainRequestLength+5)&0x0F
		int paddingBytesLength = (0xFFFFFFFF-packet.length()+5)&0x0F;
		// Padd the plain request with paddingBytesLength of value 0xff at the end
		packet.reset(packet.length());
		string end(paddingBytesLength,(UInt8)0xFF);
		packet.writeRaw(end);
	}

	WriteCRC(packet);
	
	// Encrypt the resulted request
	aesEncrypt.process(packet.begin()+4,packet.begin()+4,packet.length()-4);
}
Example #3
0
bool LUAXML::LUAToXMLElement(Exception& ex, lua_State* pState, PacketWriter& writer) {
	// -1 => table

	lua_pushstring(pState, "__name");
	lua_rawget(pState, -2);
	const char* name(lua_tostring(pState, -1));
	lua_pop(pState, 1);
	if (!name) {
		ex.set(Exception::APPLICATION, "Impossible to write a XML element without name (__name)");
		return false;
	}

	// write attributes
	writer.write("<").write(name).write(" ");
	lua_pushnil(pState);  // first key 
	while (lua_next(pState, -2) != 0) {
		// uses 'key' (at index -2) and 'value' (at index -1) 
		if (lua_type(pState, -2) == LUA_TSTRING && strcmp(lua_tostring(pState, -2),"__name")!=0 && lua_isstring(pState, -1))
			writer.write(lua_tostring(pState, -2)).write("=\"").write(lua_tostring(pState, -1)).write("\" ");
		lua_pop(pState, 1);
	}
	if (lua_objlen(pState, -1) == 0) {
		writer.write("/>");
		return true;
	}

	writer.write(">");

	// write elements
	lua_pushnil(pState);  // first key 
	while (lua_next(pState, -2) != 0) {
		// uses 'key' (at index -2) and 'value' (at index -1) 
		if (lua_isnumber(pState, -2)) {
			if (lua_istable(pState, -1))
				LUAToXMLElement(ex, pState, writer); // table child
			else if (lua_isstring(pState, -1))
				writer.write(lua_tostring(pState, -1), lua_objlen(pState, -1)); // __value
			else
				ex.set(Exception::APPLICATION, "Impossible to convert the value of type ", lua_typename(pState, lua_type(pState, -1)), "to a correct XML value of ",name);
		} else if (lua_type(pState, -2) != LUA_TSTRING)
			ex.set(Exception::APPLICATION, "Impossible to convert the key of type ",lua_typename(pState,lua_type(pState,-2)),"to a correct XML element of ",name);
		lua_pop(pState, 1);
	}

	writer.write("</").write(name).write(">");
	return true;
}
Example #4
0
void RTMFP::Pack(PacketWriter& packet,UInt32 farId) {
	PacketReader reader(packet.begin(),packet.length());
	reader.next(4);
	packet.reset(0);
	packet.write32(reader.read32()^reader.read32()^farId);
}
Example #5
0
void FlowWriter::flush(PacketWriter& writer,UInt64 stage,UInt8 flags,bool header,BinaryReader& reader,UInt16 size) {
	if(_stageAck==0 && header)
		flags |= MESSAGE_HEADER;
	if(size==0)
		flags |= MESSAGE_ABANDONMENT;
	if(_closed && _messages.size()==1) // On LAST message
		flags |= MESSAGE_END;

	// TRACE("FlowWriter %u stage %u",id,stage);

	writer.write8(flags);

	if(header) {
		writer.write7BitLongValue(id);
		writer.write7BitLongValue(stage);
		writer.write7BitLongValue(stage-_stageAck);

		// signature
		if(_stageAck==0) {
			writer.writeString8(signature);
			// No write this in the case where it's a new flow!
			if(flowId>0) {
				writer.write8(1+Util::Get7BitValueSize(flowId)); // following size
				writer.write8(0x0a); // Unknown!
				writer.write7BitLongValue(flowId);
			}
			writer.write8(0); // marker of end for this part
		}

	}

	if(size>0) {
		reader.readRaw(writer.begin()+writer.position(),size);
		writer.next(size);
	}
}
Example #6
0
// TODO make perhaps a FlowHandshake
UInt8 Handshake::handshakeHandler(UInt8 id,PacketReader& request,PacketWriter& response) {

	switch(id){
		case 0x30: {
			
			request.read8(); // passer un caractere (boite dans boite)
			UInt8 epdLen = request.read8()-1;

			
			UInt8 type = request.read8();

			string epd;
			request.readRaw(epdLen,epd);

			string tag;
			request.readRaw(16,tag);
			response.writeString8(tag);

			// UDP hole punching

			if(type == 0x0f)
				return _gateway.p2pHandshake(tag,response,peer().address,(const UInt8*)epd.c_str());

			if(type == 0x0a){
				/// Handshake
	
				// RESPONSE 38

				// New Cookie
				char cookie[65];
				RandomInputStream ris;
				ris.read(cookie,64);
				cookie[64]='\0';
				response.write8(64);
				response.writeRaw(cookie,64);
				_cookies[cookie] = new Cookie(epd);
				 
				// instance id (certificat in the middle)
				response.writeRaw(_certificat,sizeof(_certificat));
				
				return 0x70;
			} else {
				ERROR("Unkown handshake first way with '%02x' type",type);
			}
			break;
		}
		case 0x38: {
			_farId = request.read32();
			string cookie;
			request.readRaw(request.read8(),cookie);

			map<string,Cookie*>::iterator itCookie = _cookies.find(cookie.c_str());
			if(itCookie==_cookies.end()) {
				ERROR("Handshake cookie '%s' unknown",cookie.c_str());
				return 0;
			}

			request.read8(); // why 0x81?

			// signature
			string farSignature;
			request.readString8(farSignature); // 81 02 1D 02 stable

			// farPubKeyPart
			UInt8 farPubKeyPart[128];
			request.readRaw((char*)farPubKeyPart,sizeof(farPubKeyPart));

			string farCertificat;
			request.readString8(farCertificat);
			
			// peerId = SHA256(farSignature+farPubKey)
			string temp(farSignature);
			temp.append((char*)farPubKeyPart,sizeof(farPubKeyPart));
			EVP_Digest(temp.c_str(),temp.size(),(UInt8*)peer().id,NULL,EVP_sha256(),NULL);
			
			// Compute Diffie-Hellman secret
			UInt8 pubKey[128];
			UInt8 sharedSecret[128];
			RTMFP::EndDiffieHellman(RTMFP::BeginDiffieHellman(pubKey),farPubKeyPart,sharedSecret);

			// Compute Keys
			UInt8 requestKey[AES_KEY_SIZE];
			UInt8 responseKey[AES_KEY_SIZE];
			RTMFP::ComputeAsymetricKeys(sharedSecret,pubKey,_signature,farCertificat,requestKey,responseKey);

			// RESPONSE
			Util::UnpackUrl(itCookie->second->queryUrl,(string&)peer().path,(map<string,string>&)peer().parameters);
			response << _gateway.createSession(_farId,peer(),requestKey,responseKey);
			response.write8(0x81);
			response.writeString8(_signature);
			response.writeRaw((char*)pubKey,sizeof(pubKey));
			response.write8(0x58);

			// remove cookie
			_cookies.erase(itCookie);

			// db_add_public(s); // TODO

			return 0x78;
		}
		default:
			ERROR("Unkown handshake packet id '%02x'",id);
	}

	return 0;
}
Example #7
0
UInt8 RTMFPServer::p2pHandshake(const string& tag,PacketWriter& response,const SocketAddress& address,const UInt8* peerIdWanted) {

    ServerSession* pSessionWanted = (ServerSession*)_sessions.find(peerIdWanted);

    if(_pCirrus) {
        // Just to make working the man in the middle mode !

        // find the flash client equivalence
        Session* pSession = _sessions.find(address);
        if(!pSession) {
            ERROR("UDP Hole punching error : middle equivalence not found for session wanted");
            return 0;
        }

        PacketWriter& request = ((Middle*)pSession)->handshaker();
        request.write8(0x22);
        request.write8(0x21);
        request.write8(0x0F);
        request.writeRaw(pSessionWanted ? ((Middle*)pSessionWanted)->middlePeer().id : peerIdWanted,ID_SIZE);
        request.writeRaw(tag);

        ((Middle*)pSession)->sendHandshakeToTarget(0x30);
        // no response here!
        return 0;
    }


    if(!pSessionWanted) {
        DEBUG("UDP Hole punching : session %s wanted not found",Util::FormatHex(peerIdWanted,ID_SIZE).c_str())

        set<string> addresses;
        onRendezVousUnknown(peerIdWanted,addresses);
        if(addresses.empty())
            return 0;
        set<string>::const_iterator it;
        for(it=addresses.begin(); it!=addresses.end(); ++it) {
            try {
                SocketAddress address(*it);
                response.writeAddress(address,it==addresses.begin());
            } catch(Exception& ex) {
                ERROR("Bad redirection address %s, %s",(*it).c_str(),ex.displayText().c_str());
            }
        }
        return 0x71;

    } else if(pSessionWanted->failed()) {
        DEBUG("UDP Hole punching : session wanted is deleting");
        return 0;
    }

    UInt8 result = 0x00;
    if(_middle) {
        if(pSessionWanted->pTarget) {
            HelloAttempt& attempt = _handshake.helloAttempt<HelloAttempt>(tag);
            attempt.pTarget = pSessionWanted->pTarget;
            _handshake.createCookie(response,attempt,tag,"");
            response.writeRaw(&pSessionWanted->pTarget->publicKey[0],pSessionWanted->pTarget->publicKey.size());
            result = 0x70;
        } else
            ERROR("Peer/peer dumped exchange impossible : no corresponding 'Target' with the session wanted");
    }


    if(result==0x00) {
        /// Udp hole punching normal process
        UInt32 times = pSessionWanted->helloAttempt(tag);
        pSessionWanted->p2pHandshake(address,tag,times,(times>0 || address.host()==pSessionWanted->peer.address.host()) ? _sessions.find(address) : NULL);

        bool first=true;
        list<Address>::const_iterator it2;
        for(it2=pSessionWanted->peer.addresses.begin(); it2!=pSessionWanted->peer.addresses.end(); ++it2) {
            const Address& addr = *it2;
            if(addr == address)
                WARN("A client tries to connect to himself (same %s address)",address.toString().c_str());
            response.writeAddress(addr,first);
            DEBUG("P2P address initiator exchange, %s:%u",Util::FormatHex(&addr.host[0],addr.host.size()).c_str(),addr.port);
            first=false;
        }

        result = 0x71;
    }

    return result;

}
Example #8
0
UInt8 Handshake::handshakeHandler(UInt8 id,PacketReader& request,PacketWriter& response) {

	switch(id){
		case 0x30: {
			
			request.read8(); // passer un caractere (boite dans boite)
			UInt8 epdLen = request.read8()-1;

			UInt8 type = request.read8();

			string epd;
			request.readRaw(epdLen,epd);

			string tag;
			request.readRaw(16,tag);
			response.writeString8(tag);
			
			if(type == 0x0f)
				return _gateway.p2pHandshake(tag,response,peer.address,(const UInt8*)epd.c_str());

			if(type == 0x0a){
				/// Handshake
				HelloAttempt& attempt = helloAttempt<HelloAttempt>(tag);

				// Fill peer infos
				UInt16 port;
				string host;
				Util::UnpackUrl(epd,host,port,(string&)peer.path,(map<string,string>&)peer.properties);
				set<string> addresses;
				peer.onHandshake(attempt.count+1,addresses);
				if(!addresses.empty()) {
					set<string>::iterator it;
					for(it=addresses.begin();it!=addresses.end();++it) {
						try {
							if((*it)=="again")
								((string&)*it).assign(format("%s:%hu",host,port));
							SocketAddress address(*it);
							response.writeAddress(address,it==addresses.begin());
						} catch(Exception& ex) {
							ERROR("Bad redirection address %s in hello attempt, %s",(*it)=="again" ? epd.c_str() : (*it).c_str(),ex.displayText().c_str());
						}
					}
					return 0x71;
				}

				// New Cookie
				createCookie(response,attempt,tag,epd);

				// instance id (certificat in the middle)
				response.writeRaw(_certificat,sizeof(_certificat));
				return 0x70;
			} else
				ERROR("Unkown handshake first way with '%02x' type",type);
			break;
		}
		case 0x38: {
			(UInt32&)farId = request.read32();

			if(request.read7BitLongValue()!=COOKIE_SIZE) {
				ERROR("Bad handshake cookie '%s': its size should be 64 bytes",Util::FormatHex(request.current(),COOKIE_SIZE).c_str());
				return 0;
			}
	
			map<const UInt8*,Cookie*,CompareCookies>::iterator itCookie = _cookies.find(request.current());
			if(itCookie==_cookies.end()) {
				WARN("Cookie %s unknown, maybe already connected (udpBuffer congested?)",Util::FormatHex(request.current(),COOKIE_SIZE).c_str());
				return 0;
			}

			Cookie& cookie(*itCookie->second);
			(SocketAddress&)cookie.peerAddress = peer.address;

			if(cookie.farId==0) {
				((UInt32&)cookie.farId) = farId;
				request.next(COOKIE_SIZE);

				size_t size = (size_t)request.read7BitLongValue();
				// peerId = SHA256(farPubKey)
				EVP_Digest(request.current(),size,(UInt8*)cookie.peerId,NULL,EVP_sha256(),NULL);

				cookie.initiatorKey().resize(request.read7BitValue()-2);
				request.next(2); // unknown
				request.readRaw(&cookie.initiatorKey()[0],cookie.initiatorKey().size());

				cookie.initiatorNonce().resize(request.read7BitValue());
				request.readRaw(&cookie.initiatorNonce()[0],cookie.initiatorNonce().size());

				cookie.computeKeys();
			} else if(cookie.id>0) {
				// Repeat cookie reponse!
				cookie.read(response);
				return 0x78;
			} // else Keys are computing (multi-thread)

			break;
		}
		default:
			ERROR("Unkown handshake packet id %u",id);
	}

	return 0;
}
Example #9
0
void RTMFP::Pack(PacketWriter& packet,UInt32 farId) {
	PacketReader reader(packet.data(),packet.size());
	reader.next(4);
	BinaryWriter(packet.data(),4).write32(reader.read32()^reader.read32()^farId);
}
Example #10
0
void RTMFP::WriteCRC(PacketWriter& packet) {
	// Compute the CRC and add it at the beginning of the request
	PacketReader reader(packet.data(),packet.size());
	reader.next(6);
	BinaryWriter(packet.data()+4,2).write16(CheckSum(reader));
}
Example #11
0
void RTMFP::Pack(PacketWriter packet,UInt32 farId) {
	packet.reset();
	PacketReader reader(packet);
	reader.read32();
	packet.write32(reader.read32()^reader.read32()^farId);
}
Example #12
0
UInt8 Handshake::handshakeHandler(UInt8 id,PacketReader& request,PacketWriter& response) {

	switch(id){
		case 0x30: {
			
			request.read8(); // passer un caractere (boite dans boite)
			UInt8 epdLen = request.read8()-1;

			UInt8 type = request.read8();

			string epd;
			request.readRaw(epdLen,epd);

			string tag;
			request.readRaw(16,tag);
			response.writeString8(tag);
			
			if(type == 0x0f)
				return _gateway.p2pHandshake(tag,response,peer.address,(const UInt8*)epd.c_str());

			if(type == 0x0a){
				/// Handshake
				HelloAttempt& attempt = helloAttempt<HelloAttempt>(tag);
				if(edges().size()>0 && (_invoker.edgesAttemptsBeforeFallback==0 || attempt.count <_invoker.edgesAttemptsBeforeFallback)) {
					
					if(_invoker.edgesAttemptsBeforeFallback>0) {
						try {
							URI uri(epd);
							response.writeAddress(SocketAddress(uri.getHost(),uri.getPort()),false); // TODO check with true!
						} catch(Exception& ex) {
							ERROR("Parsing %s URL problem in hello attempt : %s",epd.c_str(),ex.displayText().c_str());
						}
					}

					map<int,list<Edge*> > edgesSortedByCount;
					map<string,Edge*>::const_iterator it;
					for(it=edges().begin();it!=edges().end();++it)
						edgesSortedByCount[it->second->count].push_back(it->second);

					UInt8 count=0;
					map<int,list<Edge*> >::const_iterator it2;
					for(it2=edgesSortedByCount.begin();it2!=edgesSortedByCount.end();++it2) {
						list<Edge*>::const_iterator it3;
						for(it3=it2->second.begin();it3!=it2->second.end();++it3) {
							response.writeAddress((*it3)->address,false);
							if((++count)==6) // 6 redirections maximum
								break;
						}
						if(it3!=it2->second.end())
							break;
					}
					return 0x71;

				}

				if(edges().size()>0)
					WARN("After %u hello attempts, impossible to connect to edges. Edges are busy? or unreachable?",_invoker.edgesAttemptsBeforeFallback);
	
				// New Cookie
				createCookie(response,attempt,tag,epd);
				 
				// instance id (certificat in the middle)
				response.writeRaw(_certificat,sizeof(_certificat));
				
				return 0x70;
			} else
				ERROR("Unkown handshake first way with '%02x' type",type);
			break;
		}
		case 0x39:
		case 0x38: {
			(UInt32&)farId = request.read32();

			if(request.read7BitLongValue()!=COOKIE_SIZE) {
				ERROR("Bad handshake cookie '%s': its size should be 64 bytes",Util::FormatHex(request.current(),COOKIE_SIZE).c_str());
				return 0;
			}
	
			map<const UInt8*,Cookie*,CompareCookies>::iterator itCookie = _cookies.find(request.current());
			if(itCookie==_cookies.end()) {
				if(id!=0x39) {
					ERROR("Handshake cookie '%s' unknown",Util::FormatHex(request.current(),COOKIE_SIZE).c_str());
					return 0;
				}
				Cookie* pCookie = new Cookie();
				UInt32 pos = request.position();
				request.readRaw((UInt8*)pCookie->value,COOKIE_SIZE);
				request >> (string&)pCookie->queryUrl;
				request.reset(pos);
				itCookie = _cookies.insert(pair<const UInt8*,Cookie*>(pCookie->value,pCookie)).first;
			}

			Cookie& cookie(*itCookie->second);

			if(cookie.id==0) {

				UInt8 decryptKey[AES_KEY_SIZE];UInt8* pDecryptKey=&decryptKey[0];
				UInt8 encryptKey[AES_KEY_SIZE];UInt8* pEncryptKey=&encryptKey[0];

				if(id==0x38) {
					request.next(COOKIE_SIZE);
					size_t size = (size_t)request.read7BitLongValue();
					// peerId = SHA256(farPubKey)
					EVP_Digest(request.current(),size,(UInt8*)peer.id,NULL,EVP_sha256(),NULL);

					vector<UInt8> publicKey(request.read7BitValue()-2);
					request.next(2); // unknown
					request.readRaw(&publicKey[0],publicKey.size());

					size = request.read7BitValue();

					cookie.computeKeys(&publicKey[0],publicKey.size(),request.current(),(UInt16)size,decryptKey,encryptKey);
				} else {
					// edge
					pDecryptKey=NULL;
					pEncryptKey=NULL;
					memcpy((UInt8*)peer.id,request.current(),ID_SIZE);
					request.next(COOKIE_SIZE);
					request.next(request.read7BitEncoded());
				}

				// Fill peer infos
				Util::UnpackUrl(cookie.queryUrl,(string&)peer.path,(map<string,string>&)peer.properties);

				// RESPONSE
				Session& session = _gateway.createSession(farId,peer,pDecryptKey,pEncryptKey,cookie);
				(UInt32&)cookie.id = session.id;

				string address;
				if(id==0x39) {
					// Session by edge 
					session.flags |= SESSION_BY_EDGE;
					Edge* pEdge = _invoker.edges(peer.address);
					if(!pEdge)
						ERROR("Edge session creation by an unknown server edge %s",peer.address.toString().c_str())
					else
						pEdge->addSession(session);
					request >> address;
				} else // Session direct
					address = session.peer.address.toString();

				session.peer.addresses.clear();
				session.peer.addresses.push_back(address);

				cookie.write();
			} else
void dump_to_file(PacketWriter &writer, int packet_count, PDU &packet) {
    while(packet_count--) {
        writer.write(packet);
    }
}
Example #14
0
UInt8 RTMFPHandshake::handshakeHandler(UInt8 id,PacketReader& request,PacketWriter& response) {

	switch(id){
		case 0x30: {
			
			request.next(1);
			UInt8 epdLen = request.read8()-1;

			UInt8 type = request.read8();

			string epd;
			request.readRaw(epdLen,epd);

			string tag;
			request.readRaw(16,tag);
			response.writeString8(tag);
			
			if(type == 0x0f) {

				const UInt8* peerId = (const UInt8*)epd.c_str();
				
				RTMFPSession* pSessionWanted = _sessions.find<RTMFPSession>(peerId);
	
				if(pSessionWanted) {
					if(pSessionWanted->failed())
						return 0x00; // TODO no way in RTMFP to tell "died!"
					/// Udp hole punching
					UInt32 times = attempt(tag);

					RTMFPSession* pSession(NULL);
					if(times > 0 || peer.address.host() == pSessionWanted->peer.address.host())
						pSession = _sessions.find<RTMFPSession>(peer.address);
					
					pSessionWanted->p2pHandshake(tag,peer.address,times,pSession);

					RTMFP::WriteAddress(response,pSessionWanted->peer.address, RTMFP::ADDRESS_PUBLIC);
					DEBUG("P2P address initiator exchange, ",pSessionWanted->peer.address.toString());
					for(const SocketAddress& address : pSessionWanted->peer.localAddresses) {
						RTMFP::WriteAddress(response,address, RTMFP::ADDRESS_LOCAL);
						DEBUG("P2P address initiator exchange, ",address.toString());
					}

					// add the turn address (RelayServer) if possible and required
					if (pSession && times>0) {
						UInt8 timesBeforeTurn(0);
						if(pSession->peer.parameters().getNumber("timesBeforeTurn",timesBeforeTurn) && timesBeforeTurn>=times) {
							UInt16 port = invoker.relayer.relay(pSession->peer.address,pSessionWanted->peer.address,20); // 20 sec de timeout is enough for RTMFP!
							if(port>0) {
								Exception ex;
								SocketAddress address;
								string addr;
								SocketAddress::Split(pSession->peer.serverAddress,addr);
								bool success(false);
								EXCEPTION_TO_LOG(success=address.set(ex,addr, port),"RTMFP turn impossible")
								if (success)
									RTMFP::WriteAddress(response,address, RTMFP::ADDRESS_REDIRECTION);
							} // else ERROR already display by RelayServer class
						}
					}
					return 0x71;
				}


				DEBUG("UDP Hole punching, session ", Util::FormatHex(peerId, ID_SIZE, LOG_BUFFER), " wanted not found")
				set<SocketAddress> addresses;
				peer.onRendezVousUnknown(peerId,addresses);
				set<SocketAddress>::const_iterator it;
				for(it=addresses.begin();it!=addresses.end();++it) {
					if(it->host().isWildcard())
						continue;
					if(peer.address == *it)
						WARN("A client tries to connect to himself (same ",peer.address.toString()," address)");
					RTMFP::WriteAddress(response,*it,RTMFP::ADDRESS_REDIRECTION);
					DEBUG("P2P address initiator exchange, ",it->toString());
				}
				return addresses.empty() ? 0 : 0x71;
			}
Example #15
0
void CBodyBasics::TransmitBody(INT64 nTime, int nBodyCount, IBody** ppBodies)
{
	// UDP
	Message msg;
	PacketWriter pw;
	bool ok;
	HRESULT hr;

	
	for (int cptr = 0; cptr < clients.size(); cptr++)
	{
		if (clients[cptr].active == 1) {
			//printFucker("sending to client " + clients[cptr].address + ": " + std::to_string(nBodyCount) + " bodies!\n");

			// SEND FRAME START OVER UDP
			msg.init("/beginFrame");
			msg.pushInt32(nBodyCount);
			pw.init();
			pw.startBundle().startBundle().addMessage(msg).endBundle().endBundle();
			ok = clients[cptr].socket.sendPacket(pw.packetData(), pw.packetSize());


			for (int i = 0; i < nBodyCount; ++i)
			{
				IBody* pBody = ppBodies[i];
				if (pBody)
				{
					BOOLEAN bTracked = false;
					hr = pBody->get_IsTracked(&bTracked);

					if (SUCCEEDED(hr) && bTracked)
					{
						Joint joints[JointType_Count];
						D2D1_POINT_2F jointPoints[JointType_Count];
						HandState leftHandState = HandState_Unknown;
						HandState rightHandState = HandState_Unknown;

						pBody->get_HandLeftState(&leftHandState);
						pBody->get_HandRightState(&rightHandState);

						hr = pBody->GetJoints(_countof(joints), joints);
						if (SUCCEEDED(hr))
						{
							msg.init("/beginBody");
							msg.pushInt32(i);
							pw.init();
							pw.startBundle().startBundle().addMessage(msg).endBundle().endBundle();
							ok = clients[cptr].socket.sendPacket(pw.packetData(), pw.packetSize());

							for (int j = 0; j < _countof(joints); ++j)
							{
								// /kinect body joint x y z
								msg.init("/bodyJoint");
								msg.pushInt32(i);
								msg.pushInt32(j);
								// body relative - joints[1] is spineMid which maps to Torso in OpenNI
								msg.pushFloat(joints[j].Position.X - joints[1].Position.X);
								msg.pushFloat(joints[j].Position.Y - joints[1].Position.Y);
								msg.pushFloat(joints[j].Position.Z - joints[1].Position.Z);
								// send message
								pw.init();
								pw.startBundle().startBundle().addMessage(msg).endBundle().endBundle();
								ok = clients[cptr].socket.sendPacket(pw.packetData(), pw.packetSize());

							}

							msg.init("/endBody");
							msg.pushInt32(i);
							pw.init();
							pw.startBundle().startBundle().addMessage(msg).endBundle().endBundle();
							ok = clients[cptr].socket.sendPacket(pw.packetData(), pw.packetSize());
						}


					}
				}
			}


			// SEND FRAME END OVER UDP
			msg.init("/endFrame");
			pw.init();
			pw.startBundle().startBundle().addMessage(msg).endBundle().endBundle();
			ok = clients[cptr].socket.sendPacket(pw.packetData(), pw.packetSize());


		}
	}

}