tcpObject.cc 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2013 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "tcpObject.h"
  23. #include "platform/platform.h"
  24. #include "platform/event.h"
  25. #include "game/gameInterface.h"
  26. #include "sim/simBase.h"
  27. #include "console/consoleInternal.h"
  28. #include "game/defaultGame.h"
  29. #include "collection/vector.h"
  30. #ifdef TORQUE_OS_IOS
  31. #include "platformiOS/iOSUtil.h"
  32. #endif //TORQUE_OS_IOS
  33. #include "tcpObject_ScriptBinding.h"
  34. TCPObject *TCPObject::table[TCPObject::TableSize] = {0, };
  35. IMPLEMENT_CONOBJECT(TCPObject);
  36. TCPObject *TCPObject::find(NetSocket tag)
  37. {
  38. for(TCPObject *walk = table[tag.getHash() & TableMask]; walk; walk = walk->mNext)
  39. if(walk->mTag.getHash() == tag.getHash())
  40. return walk;
  41. return NULL;
  42. }
  43. void TCPObject::addToTable(NetSocket newTag)
  44. {
  45. removeFromTable();
  46. mTag = newTag;
  47. mNext = table[mTag.getHash() & TableMask];
  48. table[mTag.getHash() & TableMask] = this;
  49. }
  50. void TCPObject::removeFromTable()
  51. {
  52. for(TCPObject **walk = &table[mTag.getHash() & TableMask]; *walk; walk = &((*walk)->mNext))
  53. {
  54. if(*walk == this)
  55. {
  56. *walk = mNext;
  57. return;
  58. }
  59. }
  60. }
  61. TCPObject::TCPObject()
  62. {
  63. mBuffer = NULL;
  64. mBufferSize = 0;
  65. mPort = 0;
  66. mTag = NetSocket::INVALID;
  67. mNext = NULL;
  68. mState = Disconnected;
  69. }
  70. TCPObject::~TCPObject()
  71. {
  72. disconnect();
  73. dFree(mBuffer);
  74. }
  75. bool TCPObject::processArguments(S32 argc, const char **argv)
  76. {
  77. if(argc == 0)
  78. return true;
  79. else if(argc == 1)
  80. {
  81. addToTable(NetSocket::fromHandle(dAtoi(argv[0])));
  82. return true;
  83. }
  84. return false;
  85. }
  86. bool TCPObject::onAdd()
  87. {
  88. if(!Parent::onAdd())
  89. return false;
  90. const char *name = getName();
  91. if(name && name[0] && getClassRep())
  92. {
  93. Namespace *parent = getClassRep()->getNameSpace();
  94. Con::linkNamespaces(parent->mName, name);
  95. mNameSpace = Con::lookupNamespace(name);
  96. }
  97. Sim::getTCPGroup()->addObject(this);
  98. return true;
  99. }
  100. U32 TCPObject::onReceive(U8 *buffer, U32 bufferLen)
  101. {
  102. // we got a raw buffer event
  103. // default action is to split the buffer into lines of text
  104. // and call processLine on each
  105. // for any incomplete lines we have mBuffer
  106. U32 start = 0;
  107. parseLine(buffer, &start, bufferLen);
  108. return start;
  109. }
  110. void TCPObject::parseLine(U8 *buffer, U32 *start, U32 bufferLen)
  111. {
  112. // find the first \n in buffer
  113. U32 i;
  114. U8 *line = buffer + *start;
  115. for(i = *start; i < bufferLen; i++)
  116. if(buffer[i] == '\n' || buffer[i] == 0)
  117. break;
  118. U32 len = i - *start;
  119. if(i == bufferLen || mBuffer)
  120. {
  121. // we've hit the end with no newline
  122. mBuffer = (U8 *) dRealloc(mBuffer, mBufferSize + len + 1);
  123. dMemcpy(mBuffer + mBufferSize, line, len);
  124. mBufferSize += len;
  125. *start = i;
  126. // process the line
  127. if(i != bufferLen)
  128. {
  129. mBuffer[mBufferSize] = 0;
  130. if(mBufferSize && mBuffer[mBufferSize-1] == '\r')
  131. mBuffer[mBufferSize - 1] = 0;
  132. U8 *temp = mBuffer;
  133. mBuffer = 0;
  134. mBufferSize = 0;
  135. processLine(temp);
  136. dFree(temp);
  137. }
  138. }
  139. else if(i != bufferLen)
  140. {
  141. line[len] = 0;
  142. if(len && line[len-1] == '\r')
  143. line[len-1] = 0;
  144. processLine(line);
  145. }
  146. if(i != bufferLen)
  147. *start = i + 1;
  148. }
  149. void TCPObject::onConnectionRequest(const NetAddress *addr, U32 connectId)
  150. {
  151. char idBuf[16];
  152. char addrBuf[256];
  153. Net::addressToString(addr, addrBuf);
  154. dSprintf(idBuf, sizeof(idBuf), "%d", connectId);
  155. Con::executef(this, 3, "onConnectRequest", addrBuf, idBuf);
  156. }
  157. bool TCPObject::processLine(U8 *line)
  158. {
  159. Con::executef(this, 2, "onLine", line);
  160. return true;
  161. }
  162. void TCPObject::onDNSResolved()
  163. {
  164. mState = DNSResolved;
  165. Con::executef(this, 1, "onDNSResolved");
  166. }
  167. void TCPObject::onDNSFailed()
  168. {
  169. mState = Disconnected;
  170. Con::executef(this, 1, "onDNSFailed");
  171. }
  172. void TCPObject::onConnected()
  173. {
  174. mState = Connected;
  175. Con::executef(this, 1, "onConnected");
  176. }
  177. void TCPObject::onConnectFailed()
  178. {
  179. mState = Disconnected;
  180. Con::executef(this, 1, "onConnectFailed");
  181. }
  182. void TCPObject::finishLastLine()
  183. {
  184. if(mBufferSize)
  185. {
  186. mBuffer[mBufferSize] = 0;
  187. processLine(mBuffer);
  188. dFree(mBuffer);
  189. mBuffer = 0;
  190. mBufferSize = 0;
  191. }
  192. }
  193. bool TCPObject::isBufferEmpty()
  194. {
  195. return (mBufferSize <= 0);
  196. }
  197. void TCPObject::emptyBuffer()
  198. {
  199. if(mBufferSize)
  200. {
  201. dFree(mBuffer);
  202. mBuffer = 0;
  203. mBufferSize = 0;
  204. }
  205. }
  206. void TCPObject::onDisconnect()
  207. {
  208. finishLastLine();
  209. mState = Disconnected;
  210. Con::executef(this, 1, "onDisconnect");
  211. }
  212. void TCPObject::listen(U16 port)
  213. {
  214. mState = Listening;
  215. NetSocket newTag = Net::openListenPort(port);
  216. addToTable(newTag);
  217. }
  218. void TCPObject::connect(const char *address)
  219. {
  220. NetSocket newTag = Net::openConnectTo(address);
  221. addToTable(newTag);
  222. }
  223. //Luma: Used to force networking to be opened before connecting... written specifically to handle GPRS/EDGE/3G situation on iPhone, but can be expanded to other platforms too
  224. void TCPObject::openAndConnect(const char *address)
  225. {
  226. #ifdef TORQUE_OS_IOS
  227. if(IsDeviceiPhone())
  228. {
  229. //on the iPhone, we need to make sure that the radio is "open" first, then call the connect CB
  230. OpeniOSNetworkingAndConnectToTCPObject(this, address);
  231. }
  232. else
  233. #endif //TORQUE_OS_IOS
  234. {
  235. //just do straight connect on non-iPhone builds for now
  236. connect(address);
  237. }
  238. }
  239. void TCPObject::disconnect()
  240. {
  241. if( mTag != NetSocket::INVALID ) {
  242. Net::closeConnectTo(mTag);
  243. }
  244. removeFromTable();
  245. mTag = NetSocket::INVALID;
  246. }
  247. //Luma: Encode data before sending via TCP so that only valid URL characters are sent
  248. U8 *TCPObject::URLEncodeData(U8 *pData, U32 iDataSize, U32 *piNewDataSize)
  249. {
  250. U8 szValidChars[] = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:/.?=_-$(){}~&";
  251. U8 *pEncodedData;
  252. U32 iCurEncodedCharacter = 0;
  253. //set initial new data size
  254. *piNewDataSize = iDataSize;
  255. //the maximum size of our encoded data is 3x the base data!
  256. pEncodedData = (U8 *)dMalloc(sizeof(U8) * iDataSize * 3);
  257. for(U32 i=0;i<iDataSize;i++)
  258. {
  259. if(!dStrchr((char *)szValidChars, pData[i]))
  260. {
  261. if(pData[i] == ' ')
  262. {
  263. //spaces become '+'
  264. pEncodedData[iCurEncodedCharacter++] = '+';
  265. }
  266. else
  267. {
  268. char szHexVal[3];
  269. dSprintf(szHexVal, 3, "%X", pData[i]);
  270. if(dStrlen(szHexVal) == 1)
  271. {
  272. //if only 1 digit was turned into text, we need to manually place the preceeding '0'
  273. szHexVal[2] = '\0';
  274. szHexVal[1] = szHexVal[0];
  275. szHexVal[0] = '0';
  276. }
  277. //invalid character... need to encode it!
  278. pEncodedData[iCurEncodedCharacter++] = '%';
  279. pEncodedData[iCurEncodedCharacter++] = szHexVal[0];
  280. pEncodedData[iCurEncodedCharacter++] = szHexVal[1];
  281. //add on 2 more to the length of the data
  282. *piNewDataSize += 2;
  283. }
  284. }
  285. else
  286. {
  287. //valid character, so leave it!
  288. pEncodedData[iCurEncodedCharacter++] = pData[i];
  289. }
  290. }
  291. return pEncodedData;
  292. }
  293. void TCPObject::send(const U8 *buffer, U32 len)
  294. {
  295. Net::sendtoSocket(mTag, buffer, S32(len));
  296. }
  297. void DefaultGame::processConnectedReceiveEvent(ConnectedReceiveEvent* event )
  298. {
  299. TCPObject *tcpo = TCPObject::find(event->tag);
  300. if(!tcpo)
  301. {
  302. Con::printf("Got bad connected receive event.");
  303. return;
  304. }
  305. U32 size = U32(event->size - ConnectedReceiveEventHeaderSize);
  306. U8 *buffer = event->data;
  307. while(size)
  308. {
  309. U32 ret = tcpo->onReceive(buffer, size);
  310. AssertFatal(ret <= size, "Invalid return size");
  311. size -= ret;
  312. buffer += ret;
  313. }
  314. //If our buffer now has something in it then it's probably a web socket packet and lets handle it
  315. if(!tcpo->isBufferEmpty())
  316. {
  317. //Copy all the data into a string (may be a quicker way of doing this)
  318. U8 *data = event->data;
  319. //Send the packet to script
  320. bool handled = Con::executef(tcpo, 2, "onPacket", data);
  321. //If the script did something with it, clear the buffer
  322. if(handled)
  323. {
  324. tcpo->emptyBuffer();
  325. }
  326. }
  327. Con::executef(tcpo, 1, "onEndReceive");
  328. }
  329. void DefaultGame::processConnectedAcceptEvent( ConnectedAcceptEvent* event )
  330. {
  331. TCPObject *tcpo = TCPObject::find(event->portTag);
  332. if(!tcpo)
  333. return;
  334. tcpo->onConnectionRequest(&event->address, event->connectionTag.getHandle());
  335. }
  336. void DefaultGame::processConnectedNotifyEvent( ConnectedNotifyEvent* event )
  337. {
  338. TCPObject *tcpo = TCPObject::find(event->tag);
  339. if(!tcpo)
  340. return;
  341. switch(event->state)
  342. {
  343. case ConnectedNotifyEvent::DNSResolved:
  344. tcpo->onDNSResolved();
  345. break;
  346. case ConnectedNotifyEvent::DNSFailed:
  347. tcpo->onDNSFailed();
  348. break;
  349. case ConnectedNotifyEvent::Connected:
  350. tcpo->onConnected();
  351. break;
  352. case ConnectedNotifyEvent::ConnectFailed:
  353. tcpo->onConnectFailed();
  354. break;
  355. case ConnectedNotifyEvent::Disconnected:
  356. tcpo->onDisconnect();
  357. break;
  358. }
  359. }