From c74bad6aa8603875d0c623d25509088e0e711a91 Mon Sep 17 00:00:00 2001 From: Bernhard Schelling <14200249+schellingb@users.noreply.github.com> Date: Sat, 7 Mar 2020 23:37:12 +0900 Subject: [PATCH] Renamed ZL_WebSocketConnection to ZL_WebSocketClient Added ZL_WebSocketServer for non-web platforms Unified and simplified various network APIs --- Emscripten/ZillaLibEmscripten.js | 36 +- Include/ZL_Data.h | 5 +- Include/ZL_Network.h | 175 ++++-- Source/ZL_Data.cpp | 110 +++- Source/ZL_Network.cpp | 982 +++++++++++++++++++------------ Source/ZL_Platform.h | 19 +- Source/ZL_PlatformEmscripten.cpp | 38 +- Source/ZL_PlatformNACL.cpp | 52 +- Source/ZL_PlatformNACL.h | 4 +- WebAssembly/ZillaLibWasm.js | 27 +- WebAssembly/ZillaLibWasm.mk | 14 +- 11 files changed, 922 insertions(+), 540 deletions(-) diff --git a/Emscripten/ZillaLibEmscripten.js b/Emscripten/ZillaLibEmscripten.js index 8c00506..25184cd 100644 --- a/Emscripten/ZillaLibEmscripten.js +++ b/Emscripten/ZillaLibEmscripten.js @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2018 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -197,11 +197,12 @@ var LibraryZLJS = ZLJS.do_lock(flag); }, - ZLJS_AsyncLoad: function(url, impl, postdata, postlength) + ZLJS_AsyncLoad: function(url, impl, postdata, postlength, timeout) { var xhr = new XMLHttpRequest(); - xhr.open((postlength ? 'POST' : 'GET'), Pointer_stringify(url), true); + xhr.open((postlength ? 'POST' : 'GET'), UTF8ToString(url), true); xhr.responseType = 'arraybuffer'; + xhr.timeout = timeout; xhr.onload = function() { if (xhr.status == 200) @@ -212,9 +213,10 @@ var LibraryZLJS = } else _ZLFNHTTP(impl, xhr.status, 0, 0); }; - xhr.onerror = function(event) + xhr.ontimeout = xhr.onerror = function(event) { - _ZLFNHTTP(impl, xhr.status, 0, 0); + // this could be called synchronously by xhr.send() so force it to arrive a frame later + setTimeout(function() { _ZLFNHTTP(impl, xhr.status||-1, 0, 0); }); }; if (postlength) try { xhr.send(HEAPU8.subarray(postdata, postdata+postlength)); } catch (e) { xhr.send(HEAPU8.buffer.slice(postdata, postdata+postlength)); } else xhr.send(null); @@ -223,19 +225,19 @@ var LibraryZLJS = ZLJS_Websocket__deps: ['$ZLJS'], ZLJS_Websocket: function(impl, cmd, param, len) { - if (cmd == 1) { if (ZLJS.ws) ZLJS.ws.send(Pointer_stringify(param,len)); return; } + if (cmd == 1) { if (ZLJS.ws) ZLJS.ws.send(UTF8ToString(param,len)); return; } if (cmd == 2) { if (ZLJS.ws) ZLJS.ws.send(HEAPU8.subarray(param, param+len)); return; } - if (cmd >= 3) { ZLJS.ws.close(cmd-3, len?Pointer_stringify(param,len):undefined); ZLJS.ws = undefined; return; } - var w = new WebSocket(Pointer_stringify(param)); + if (cmd >= 3) { if (ZLJS.ws) ZLJS.ws.close(cmd-3, len?UTF8ToString(param,len):undefined); ZLJS.ws = undefined; return; } + var w = new WebSocket(UTF8ToString(param)); w.binaryType = 'arraybuffer'; - w.onopen = function() { _ZLFNWebSocket(impl, 1); } + w.onopen = function() { _ZLFNWebSocket(impl, 0); } w.onmessage = function (evt) { var s = typeof evt.data === 'string', v = s ? evt.data : new Uint8Array(evt.data), b = s ? ZLJS.malloc_string(v) : allocate(v, 'i8', ALLOC_NORMAL); - _ZLFNWebSocket(impl, s ? 2 : 3, b, v.length); + _ZLFNWebSocket(impl, s ? 1 : 2, b, v.length); _free(b); } - w.onclose = function() { _ZLFNWebSocket(impl, 0); ZLJS.ws = undefined; } + w.onclose = function(evt) { _ZLFNWebSocket(impl, 3+evt.code); ZLJS.ws = undefined; } ZLJS.ws = w; }, @@ -286,7 +288,7 @@ var LibraryZLJS = ZLJS_OpenExternalUrl__deps: ['$ZLJS'], ZLJS_OpenExternalUrl: function(url) { - url = Pointer_stringify(url); + url = UTF8ToString(url); if (Module['openUrl']) Module['openUrl'](url); else window.open(url, '_newtab'); }, @@ -294,31 +296,31 @@ var LibraryZLJS = ZLJS_SettingsInit__deps: ['$ZLJS'], ZLJS_SettingsInit: function(prefix) { - ZLJS.settings_prefix = (Module['settingsPrefix'] ? Module['settingsPrefix'] : Pointer_stringify(prefix)); + ZLJS.settings_prefix = (Module['settingsPrefix'] ? Module['settingsPrefix'] : UTF8ToString(prefix)); }, ZLJS_SettingsSet__deps: ['$ZLJS'], ZLJS_SettingsSet: function(key, val) { - if (localStorage) localStorage[ZLJS.settings_prefix +' '+Pointer_stringify(key)] = Pointer_stringify(val); + if (localStorage) localStorage[ZLJS.settings_prefix +' '+UTF8ToString(key)] = UTF8ToString(val); }, ZLJS_SettingsDel__deps: ['$ZLJS'], ZLJS_SettingsDel: function(key) { - if (localStorage) localStorage.removeItem(ZLJS.settings_prefix +' '+Pointer_stringify(key)); + if (localStorage) localStorage.removeItem(ZLJS.settings_prefix +' '+UTF8ToString(key)); }, ZLJS_SettingsHas__deps: ['$ZLJS'], ZLJS_SettingsHas: function(key) { - return (localStorage && localStorage[ZLJS.settings_prefix +' '+Pointer_stringify(key)] !== undefined); + return (localStorage && localStorage[ZLJS.settings_prefix +' '+UTF8ToString(key)] !== undefined); }, ZLJS_SettingsGetMalloc__deps: ['$ZLJS'], ZLJS_SettingsGetMalloc: function(key) { - key = Pointer_stringify(key); + key = UTF8ToString(key); if (!localStorage || localStorage[ZLJS.settings_prefix +' '+key] === undefined) { return 0; } return ZLJS.malloc_string(localStorage[ZLJS.settings_prefix +' '+key]); }, diff --git a/Include/ZL_Data.h b/Include/ZL_Data.h index 18aa1a9..aa7a389 100644 --- a/Include/ZL_Data.h +++ b/Include/ZL_Data.h @@ -190,7 +190,7 @@ struct ZL_Compression { enum { NO_COMPRESSION = 0, BEST_SPEED = 1, BEST_COMPRESSION = 9, DEFAULT_COMPRESSION = 6 }; - //Compress data into an automatically sized output vector and return size of output (if ReserveMaxCompressSize is false, multiple re-allocations might be made) + //Compress data and append it to an automatically sized output vector and return the compressed size (if ReserveMaxCompressSize is false, multiple re-allocations might be made) static size_t Compress(const void* DecompressedBuffer, size_t DecompressedSize, std::vector& Output, int Level = DEFAULT_COMPRESSION, bool ReserveMaxCompressSize = false); static inline size_t Compress(const std::vector& Decompressed, std::vector& Output, int Level = DEFAULT_COMPRESSION, bool ReserveMaxCompressSize = false) { return Compress((Decompressed.empty() ? NULL : &Decompressed[0]), Decompressed.size(), Output, Level, ReserveMaxCompressSize); } @@ -200,7 +200,7 @@ struct ZL_Compression //Compress data into a fixed size buffer and returns true if the given buffer was large enough (CompressedSize will be modified to actual size) static bool Compress(const void* DecompressedBuffer, size_t DecompressedSize, const void* CompressedBuffer, size_t* CompressedSize, int Level = DEFAULT_COMPRESSION); - //Decompress data into an automatically sized output vector and return size of output (if DecompressedSizeHint is not large enough, multiple re-allocations might be made) + //Decompress and append data to an automatically sized output vector and return size of output (if DecompressedSizeHint is not large enough, multiple re-allocations might be made) static size_t Decompress(const void* CompressedBuffer, size_t CompressedSize, std::vector& Output, size_t DecompressedSizeHint = 0); static inline size_t Decompress(const std::vector& Compressed, std::vector& Output, size_t DecompressedSizeHint = 0) { return Decompress((Compressed.empty() ? NULL : &Compressed[0]), Compressed.size(), Output, DecompressedSizeHint); } @@ -214,6 +214,7 @@ struct ZL_Checksum static unsigned int CRC32(const void* Data, size_t DataSize); static unsigned int Fast(const void* Data, size_t DataSize); static unsigned int Fast4(const void* Data, size_t DataSize); //DataSize must be 4 byte aligned + static void SHA1(const void* Data, size_t DataSize, unsigned char OutResult[20]); }; inline size_t ZL_Hash(i64 x) { x *= 0xff51afd7ed558ccd; return (size_t)(x ^ (x >> 32)); } diff --git a/Include/ZL_Network.h b/Include/ZL_Network.h index bdb28ed..8b8565c 100644 --- a/Include/ZL_Network.h +++ b/Include/ZL_Network.h @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -29,6 +29,12 @@ #define ZL_NO_SOCKETS #endif +struct ZL_Network +{ + static bool Init(); + static void DeInit(); +}; + #ifndef ZL_NO_SOCKETS #include @@ -59,7 +65,7 @@ struct ZL_Peer { unsigned int host; unsigned short port; - void** data; + void* data; ZL_PeerHandle handle; }; @@ -78,23 +84,40 @@ struct ZL_Server void Open(int server_port, unsigned short max_connections, const char *bind_host = NULL, unsigned char num_channels = 1); void OpenP2P(const char *p2prelay_host, int p2prelay_port, unsigned short max_connections = 2, const void* relaykey = NULL, unsigned char relaykey_length = 0, unsigned char num_channels = 1); void Close(unsigned int closemsg = 0); - void Send(ZL_PeerHandle peerhandle, const ZL_Packet& packet); - void Send(ZL_PeerHandle peerhandle, const void* data, size_t length, unsigned char channel = 0, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE); - void Send(const std::vector& peerhandles, const ZL_Packet& packet); - void Send(const std::vector& peerhandles, const void* data, size_t length, unsigned char channel = 0, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE); - void Broadcast(const ZL_Packet& packet); - void Broadcast(const ZL_Packet& packet, ZL_PeerHandle peerhandle_except); - void Broadcast(const void* data, size_t length, unsigned char channel = 0, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE); - void Broadcast(const void* data, size_t length, ZL_PeerHandle peerhandle_except, unsigned char channel = 0, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE); + + //Send to a single client + void Send(ZL_PeerHandle handle, const void* data, size_t length, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0); + void Send(ZL_PeerHandle handle, const ZL_Packet& packet) { Send(handle, packet.data, packet.length, packet.type, packet.channel); } + void Send(ZL_PeerHandle handle, const char *data, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send(handle, data, (data ? strlen(data) : 0), type, channel); } + void Send(ZL_PeerHandle handle, const ZL_String& str, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send(handle, (str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), type, channel); } + template void Send(ZL_PeerHandle handle, const T& packet, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send(handle, &packet, sizeof(packet), type, channel); } + + //Send to all clients + void Broadcast(const void* data, size_t length, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0); + void Broadcast(const ZL_Packet& packet) { Broadcast(packet.data, packet.length, packet.type, packet.channel); } + void Broadcast(const char *data, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast(data, (data ? strlen(data) : 0), type, channel); } + void Broadcast(const ZL_String& str, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), type, channel); } + template void Broadcast(const T& packet, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast(&packet, sizeof(packet), type, channel); } + + //Send to all clients except one + void Broadcast(const void* data, size_t length, ZL_PeerHandle handle_except, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0); + void Broadcast(const ZL_Packet& packet, ZL_PeerHandle handle_except) { Broadcast(packet.data, packet.length, handle_except, packet.type, packet.channel); } + void Broadcast(const char *data, ZL_PeerHandle handle_except, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast(data, (data ? strlen(data) : 0), handle_except, type, channel); } + void Broadcast(const ZL_String& str, ZL_PeerHandle handle_except, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), handle_except, type, channel); } + template void Broadcast(const T& packet, ZL_PeerHandle handle_except, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Broadcast(&packet, sizeof(packet), handle_except, type, channel); } + + void DisconnectPeer(ZL_PeerHandle handle, unsigned int closemsg = 0); void GetPeerHandles(std::vector& out_list); void GetPeerDetails(std::vector& out_list); + void SetPeerData(ZL_PeerHandle handle, void* data); + void SetPeerData(const ZL_Peer& peer, void* data); bool IsOpened(); size_t GetPeerCount(); bool IsP2PMaster(); - ZL_Signal_v1& sigConnected(); - ZL_Signal_v1& sigDisconnected(); - ZL_Signal_v2& sigReceived(); + ZL_Signal_v1& sigConnected(); + ZL_Signal_v2& sigDisconnected(); + ZL_Signal_v2& sigReceived(); private: struct ZL_Server_Impl* impl; }; @@ -113,12 +136,16 @@ struct ZL_Client void Connect(const char *host, int port, unsigned char num_channels = 1); void Disconnect(unsigned int closemsg = 0); - void Send(ZL_Packet &packet); - void Send(const void* data, size_t length, unsigned char channel = 0, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE); - bool IsConnected(); + void Send(const void* data, size_t length, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0); + void Send(const ZL_Packet& packet) { Send(packet.data, packet.length, packet.type, packet.channel); } + void Send(const char *data, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send(data, (data ? strlen(data) : 0), type, channel); } + void Send(const ZL_String& str, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), type, channel); } + template void Send(const T& packet, ZL_Packet_Reliability type = ZL_PACKET_RELIABLE, unsigned char channel = 0) { Send(&packet, sizeof(packet), type, channel); } + + bool IsConnected(); ZL_Signal_v0& sigConnected(); - ZL_Signal_v0& sigDisconnected(); + ZL_Signal_v1& sigDisconnected(); ZL_Signal_v1& sigReceived(); private: struct ZL_Client_Impl* impl; @@ -145,12 +172,82 @@ struct ZL_RawSocket private: struct ZL_RawSocket_Impl* impl; }; +struct ZL_WebSocketServer +{ + ZL_WebSocketServer(); + ZL_WebSocketServer(int server_port, unsigned short max_connections, const char *bind_host = NULL); + ~ZL_WebSocketServer(); + ZL_WebSocketServer(const ZL_WebSocketServer &source); + ZL_WebSocketServer &operator =(const ZL_WebSocketServer &source); + operator bool () const { return (impl!=NULL); } + bool operator==(const ZL_WebSocketServer &b) const { return (impl==b.impl); } + bool operator!=(const ZL_WebSocketServer &b) const { return (impl!=b.impl); } + + void Open(int server_port, unsigned short max_connections, const char *bind_host = NULL); + void Close(short code = 0); + + //Send to one client + void Send(ZL_PeerHandle handle, const void* data, size_t length, bool is_text = false); + void Send(ZL_PeerHandle handle, const char *data, bool is_text = true) { Send(handle, data, (data ? strlen(data) : 0), is_text); } + void Send(ZL_PeerHandle handle, const ZL_String& str, bool is_text = true) { Send(handle, (str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), is_text); } + template void Send(ZL_PeerHandle handle, const T& packet) { Send(handle, &packet, sizeof(packet), false); } + + //Send to all clients + void Broadcast(const void* data, size_t length, bool is_text = false); + void Broadcast(const char *data, bool is_text = true) { Broadcast(data, (data ? strlen(data) : 0), is_text); } + void Broadcast(const ZL_String& str, bool is_text = true) { Broadcast((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), is_text); } + template void Broadcast(const T& packet) { Broadcast(&packet, sizeof(packet), false); } + + //Send to all clients except one + void Broadcast(const void* data, size_t length, ZL_PeerHandle handle_except, bool is_text = false); + void Broadcast(const char *data, ZL_PeerHandle handle_except, bool is_text = true) { Broadcast(data, (data ? strlen(data) : 0), handle_except, is_text); } + void Broadcast(const ZL_String& str, ZL_PeerHandle handle_except, bool is_text = true) { Broadcast((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), handle_except, is_text); } + template void Broadcast(const T& packet, ZL_PeerHandle handle_except) { Broadcast(&packet, sizeof(packet), handle_except, false); } + + void DisconnectPeer(ZL_PeerHandle handle, short code = 0); + void GetPeerHandles(std::vector& out_list); + void GetPeerDetails(std::vector& out_list); + void SetPeerData(ZL_PeerHandle handle, void* data); + void SetPeerData(const ZL_Peer& peer, void* data); + bool IsOpened(); + size_t GetPeerCount(); + + ZL_Signal_v2& sigConnected(); + ZL_Signal_v2& sigDisconnected(); + ZL_Signal_v2& sigReceivedText(); + ZL_Signal_v3& sigReceivedBinary(); + + private: struct ZL_WebSocketServer_Impl* impl; +}; + #endif //ZL_NO_SOCKETS -struct ZL_Network +struct ZL_WebSocketClient { - static bool Init(); - static void DeInit(); + ZL_WebSocketClient(); + ZL_WebSocketClient(const char *url); + ~ZL_WebSocketClient(); + ZL_WebSocketClient(const ZL_WebSocketClient &source); + ZL_WebSocketClient &operator =(const ZL_WebSocketClient &source); + operator bool () const { return (impl!=NULL); } + bool operator==(const ZL_WebSocketClient &b) const { return (impl==b.impl); } + bool operator!=(const ZL_WebSocketClient &b) const { return (impl!=b.impl); } + + void Connect(const char *url); + void Disconnect(unsigned short code = 1000, const char *reason = NULL, size_t reason_length = 0) const; + bool IsConnected() const; + + void Send(const void* data, size_t length, bool is_text = false); + void Send(const char *data, bool is_text = true) { Send(data, (data ? strlen(data) : 0), is_text); } + void Send(const ZL_String& str, bool is_text = true) { Send((str.empty() ? 0 : &*str.begin()), (str.empty() ? 0 : str.size()), is_text); } + template void Send(const T& packet) { Send(&packet, sizeof(packet), false); } + + ZL_Signal_v0& sigConnected(); + ZL_Signal_v1& sigDisconnected(); + ZL_Signal_v1& sigReceivedText(); + ZL_Signal_v2& sigReceivedBinary(); + + private: struct ZL_WebSocketClient_Impl* impl; }; struct ZL_HttpConnection @@ -163,20 +260,17 @@ struct ZL_HttpConnection operator bool () const { return (impl!=NULL); } bool operator==(const ZL_HttpConnection &b) const { return (impl==b.impl); } bool operator!=(const ZL_HttpConnection &b) const { return (impl!=b.impl); } - - ZL_HttpConnection& SetURL(const char *url); ZL_HttpConnection& SetPostData(const char *data); //zero terminated string ZL_HttpConnection& SetPostData(const void* data, size_t length); + ZL_HttpConnection& ClearPostData(); //Stream data in packets with a final zero length terminating packet ZL_HttpConnection& SetDoStreamData(bool DoStreamData = true); - #ifndef ZL_NO_SOCKETS - //set custom timeout for http request, most platforms use a appropriate default + //set custom timeout for http request (default 10 seconds) ZL_HttpConnection& SetTimeout(unsigned int timeout_msec = 10000); - #endif - void Connect() const; + void Connect(const char *url); ZL_Signal_v2& sigReceivedString(); ZL_Signal_v3& sigReceivedData(); @@ -184,33 +278,4 @@ struct ZL_HttpConnection private: struct ZL_HttpConnection_Impl* impl; }; -struct ZL_WebSocketConnection -{ - ZL_WebSocketConnection(); - ZL_WebSocketConnection(const char *url); - ~ZL_WebSocketConnection(); - ZL_WebSocketConnection(const ZL_WebSocketConnection &source); - ZL_WebSocketConnection &operator =(const ZL_WebSocketConnection &source); - operator bool () const { return (impl!=NULL); } - bool operator==(const ZL_WebSocketConnection &b) const { return (impl==b.impl); } - bool operator!=(const ZL_WebSocketConnection &b) const { return (impl!=b.impl); } - - ZL_WebSocketConnection& SetURL(const char *url); - ZL_WebSocketConnection& SendText(const char *data); - ZL_WebSocketConnection& SendText(const char *data, size_t length); - ZL_WebSocketConnection& SendText(const ZL_String& str); - ZL_WebSocketConnection& SendBinary(const void* data, size_t length); - - void Connect() const; - void Disconnect(unsigned short code = 1000, const char *reason = NULL, size_t reason_length = 0) const; - bool IsConnected() const; - - ZL_Signal_v1& sigReceivedText(); - ZL_Signal_v2& sigReceivedBinary(); - ZL_Signal_v0& sigConnected(); - ZL_Signal_v0& sigDisconnected(); - - private: struct ZL_WebSocketConnection_Impl* impl; -}; - #endif //__ZL_NETWORK__ diff --git a/Source/ZL_Data.cpp b/Source/ZL_Data.cpp index 2cb8891..07424bf 100644 --- a/Source/ZL_Data.cpp +++ b/Source/ZL_Data.cpp @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -916,22 +916,22 @@ size_t ZL_Compression::Compress(const void* InBuf, size_t InSize, std::vector> (32 - (bits)))) + #define SHA1BLK0(i) (block[i] = (SHA1ROL(block[i],24)&0xFF00FF00)|(SHA1ROL(block[i],8)&0x00FF00FF)) + #define SHA1BLK(i) (block[i&15] = SHA1ROL(block[(i+13)&15]^block[(i+8)&15]^block[(i+2)&15]^block[i&15],1)) + #define SHA1R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+SHA1BLK0(i)+0x5A827999+SHA1ROL(v,5);w=SHA1ROL(w,30); + #define SHA1R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+SHA1BLK(i)+0x5A827999+SHA1ROL(v,5);w=SHA1ROL(w,30); + #define SHA1R2(v,w,x,y,z,i) z+=(w^x^y)+SHA1BLK(i)+0x6ED9EBA1+SHA1ROL(v,5);w=SHA1ROL(w,30); + #define SHA1R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+SHA1BLK(i)+0x8F1BBCDC+SHA1ROL(v,5);w=SHA1ROL(w,30); + #define SHA1R4(v,w,x,y,z,i) z+=(w^x^y)+SHA1BLK(i)+0xCA62C1D6+SHA1ROL(v,5);w=SHA1ROL(w,30); + // 4 rounds of 20 operations each. Loop unrolled. + SHA1R0(a,b,c,d,e, 0); SHA1R0(e,a,b,c,d, 1); SHA1R0(d,e,a,b,c, 2); SHA1R0(c,d,e,a,b, 3); + SHA1R0(b,c,d,e,a, 4); SHA1R0(a,b,c,d,e, 5); SHA1R0(e,a,b,c,d, 6); SHA1R0(d,e,a,b,c, 7); + SHA1R0(c,d,e,a,b, 8); SHA1R0(b,c,d,e,a, 9); SHA1R0(a,b,c,d,e,10); SHA1R0(e,a,b,c,d,11); + SHA1R0(d,e,a,b,c,12); SHA1R0(c,d,e,a,b,13); SHA1R0(b,c,d,e,a,14); SHA1R0(a,b,c,d,e,15); + SHA1R1(e,a,b,c,d,16); SHA1R1(d,e,a,b,c,17); SHA1R1(c,d,e,a,b,18); SHA1R1(b,c,d,e,a,19); + SHA1R2(a,b,c,d,e,20); SHA1R2(e,a,b,c,d,21); SHA1R2(d,e,a,b,c,22); SHA1R2(c,d,e,a,b,23); + SHA1R2(b,c,d,e,a,24); SHA1R2(a,b,c,d,e,25); SHA1R2(e,a,b,c,d,26); SHA1R2(d,e,a,b,c,27); + SHA1R2(c,d,e,a,b,28); SHA1R2(b,c,d,e,a,29); SHA1R2(a,b,c,d,e,30); SHA1R2(e,a,b,c,d,31); + SHA1R2(d,e,a,b,c,32); SHA1R2(c,d,e,a,b,33); SHA1R2(b,c,d,e,a,34); SHA1R2(a,b,c,d,e,35); + SHA1R2(e,a,b,c,d,36); SHA1R2(d,e,a,b,c,37); SHA1R2(c,d,e,a,b,38); SHA1R2(b,c,d,e,a,39); + SHA1R3(a,b,c,d,e,40); SHA1R3(e,a,b,c,d,41); SHA1R3(d,e,a,b,c,42); SHA1R3(c,d,e,a,b,43); + SHA1R3(b,c,d,e,a,44); SHA1R3(a,b,c,d,e,45); SHA1R3(e,a,b,c,d,46); SHA1R3(d,e,a,b,c,47); + SHA1R3(c,d,e,a,b,48); SHA1R3(b,c,d,e,a,49); SHA1R3(a,b,c,d,e,50); SHA1R3(e,a,b,c,d,51); + SHA1R3(d,e,a,b,c,52); SHA1R3(c,d,e,a,b,53); SHA1R3(b,c,d,e,a,54); SHA1R3(a,b,c,d,e,55); + SHA1R3(e,a,b,c,d,56); SHA1R3(d,e,a,b,c,57); SHA1R3(c,d,e,a,b,58); SHA1R3(b,c,d,e,a,59); + SHA1R4(a,b,c,d,e,60); SHA1R4(e,a,b,c,d,61); SHA1R4(d,e,a,b,c,62); SHA1R4(c,d,e,a,b,63); + SHA1R4(b,c,d,e,a,64); SHA1R4(a,b,c,d,e,65); SHA1R4(e,a,b,c,d,66); SHA1R4(d,e,a,b,c,67); + SHA1R4(c,d,e,a,b,68); SHA1R4(b,c,d,e,a,69); SHA1R4(a,b,c,d,e,70); SHA1R4(e,a,b,c,d,71); + SHA1R4(d,e,a,b,c,72); SHA1R4(c,d,e,a,b,73); SHA1R4(b,c,d,e,a,74); SHA1R4(a,b,c,d,e,75); + SHA1R4(e,a,b,c,d,76); SHA1R4(d,e,a,b,c,77); SHA1R4(c,d,e,a,b,78); SHA1R4(b,c,d,e,a,79); + // Add the working vars back into context.state[] + state[0] += a; state[1] += b; state[2] += c; state[3] += d; state[4] += e; + } + + static void SHA1Process(SHA1_CTX* context, const unsigned char* data, size_t len) + { + size_t i, j = context->count[0]; + if ((context->count[0] += (len << 3)) < j) context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) SHA1Transform(context->state, &data[i]); + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); + } + + size_t count[2]; + unsigned int state[5]; + unsigned char buffer[64]; + }; + + // Initialize new context with initialization constants + SHA1_CTX ctx; + ctx.count[0] = ctx.count[1] = 0; + ctx.state[0] = 0x67452301; + ctx.state[1] = 0xEFCDAB89; + ctx.state[2] = 0x98BADCFE; + ctx.state[3] = 0x10325476; + ctx.state[4] = 0xC3D2E1F0; + + SHA1_CTX::SHA1Process(&ctx, (const unsigned char*)Data, DataSize); + + // Add padding and return the message digest + unsigned char finalcount[8]; + for (unsigned i = 0; i < 8; i++) finalcount[i] = (unsigned char)((ctx.count[(i >= 4 ? 0 : 1)] >> ((3-(i & 3)) * 8) ) & 255); + unsigned char c = 0200; + SHA1_CTX::SHA1Process(&ctx, &c, 1); + while ((ctx.count[0] & 504) != 448) { c = 0000; SHA1_CTX::SHA1Process(&ctx, &c, 1); } + SHA1_CTX::SHA1Process(&ctx, finalcount, 8); + for (unsigned j = 0; j < 20; j++) OutResult[j] = (unsigned char)((ctx.state[j>>2] >> ((3-(j & 3)) * 8) ) & 255); +} diff --git a/Source/ZL_Network.cpp b/Source/ZL_Network.cpp index 37c3441..f6e621c 100644 --- a/Source/ZL_Network.cpp +++ b/Source/ZL_Network.cpp @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2018 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -26,54 +26,51 @@ #ifdef ZL_USE_ENET +#include "ZL_Data.h" #include "enet/enet.h" #include -struct ZL_Connection_Impl; -static std::vector *OpenConnections = NULL; +static std::vector *OpenConnections = NULL; static ZL_MutexHandle ZL_OpenConnectionsMutex; - -#define SWAPBE32(v) (((v & 0xFF) << 24) | ((v & 0xFF00) << 8) | ((v >> 8) & 0xFF00) | ((v >> 24) & 0xFF)) +static void _ZL_Network_KeepAlive(); struct ZL_Connection_Impl : ZL_Impl { virtual bool KeepAlive() = 0; virtual void ShutDown() = 0; -}; - -static void OpenConnectionsAdd(ZL_Connection_Impl* c) -{ - ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); - ZL_MutexLock(ZL_OpenConnectionsMutex); - for (std::vector::iterator it = OpenConnections->begin(); it != OpenConnections->end(); ++it) - if (*it == c) goto skipadd; - //c->AddRef(); - OpenConnections->push_back(c); - skipadd: - ZL_MutexUnlock(ZL_OpenConnectionsMutex); -} + bool connectionWasClosed; + +protected: + ~ZL_Connection_Impl() + { + ZL_MutexLock(ZL_OpenConnectionsMutex); + for (std::vector::iterator it = OpenConnections->begin(); it != OpenConnections->end(); ++it) + if (*it == this) { OpenConnections->erase(it); break; } + ZL_MutexUnlock(ZL_OpenConnectionsMutex); + } -static void OpenConnectionsDel(ZL_Connection_Impl* c) -{ - ZL_MutexLock(ZL_OpenConnectionsMutex); - for (std::vector::iterator it = OpenConnections->begin(); it != OpenConnections->end(); ++it) - if (*it == c) - { - //c->DelRef(); - OpenConnections->erase(it); - break; - } - ZL_MutexUnlock(ZL_OpenConnectionsMutex); -} + void OpenConnectionsAdd() + { + ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); + connectionWasClosed = false; + ZL_MutexLock(ZL_OpenConnectionsMutex); + for (std::vector::iterator it = OpenConnections->begin(); it != OpenConnections->end(); ++it) + if (*it == this) goto skipadd; + OpenConnections->push_back(this); + skipadd: + ZL_MutexUnlock(ZL_OpenConnectionsMutex); + } +}; static void _ZL_Network_KeepAlive() { ZL_MutexLock(ZL_OpenConnectionsMutex); for (std::vector::iterator it = OpenConnections->begin(); it != OpenConnections->end();) { - if (!(*it)->KeepAlive()) + if ((*it)->connectionWasClosed || !(*it)->KeepAlive()) it = OpenConnections->erase(it); - else ++it; + else + ++it; } ZL_MutexUnlock(ZL_OpenConnectionsMutex); } @@ -103,26 +100,30 @@ void ZL_Network::DeInit() ZL_MutexDestroy(ZL_OpenConnectionsMutex); } +static char* GetHTTPContentStart(char* buffer, size_t length) +{ + for (char *content = buffer, *end = buffer + length; content <= end-2; content++) + if ((content[0]=='\n'&&content[1]=='\n') || (content <= end-4 && content[0]=='\r'&&content[1]=='\n'&&content[2]=='\r'&&content[3]=='\n')) + return content + (content[0]=='\n' ? 2 : 4); + return NULL; +} + //---------------------------------------------------------------------------------------------------------------------------------------------- struct ZL_Server_Impl : ZL_Connection_Impl { ENetHost *host; struct sP2P { bool (*doKeepAlive)(ZL_Server_Impl*); void (*doClose)(ZL_Server_Impl*); ENetAddress relay_server; unsigned char relaykey[0xFF], relaykey_length, is_master; ticks_t time_open; } *P2P; - ZL_Signal_v1 sigConnected; - ZL_Signal_v1 sigDisconnected; - ZL_Signal_v2 sigReceived; + ZL_Signal_v1 sigConnected; + ZL_Signal_v2 sigDisconnected; + ZL_Signal_v2 sigReceived; ZL_Server_Impl() : host(NULL), P2P(NULL) { } - ~ZL_Server_Impl() { Close(0, true); } - - void ShutDown() { Close(0, false); } - void Open(int server_port, unsigned short num_connections, const char *bind_host, unsigned char num_channels) { ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); - if (host) Close(0, true); + if (host) Close(0); ENetAddress address; if (bind_host) enet_address_set_host(&address, bind_host); @@ -131,26 +132,13 @@ struct ZL_Server_Impl : ZL_Connection_Impl host = enet_host_create(&address, num_connections, num_channels, 0, 0); if (!host) return; - OpenConnectionsAdd(this); + OpenConnectionsAdd(); } - - void Close(unsigned int closemsg, bool remove_openconnection) - { - if (!host) return; - for (ENetPeer* it = host->peers; it != &host->peers[host->peerCount]; ++it) - if (it->state == ENET_PEER_STATE_CONNECTED) - enet_peer_disconnect(it, closemsg); - if (P2P) P2P->doClose(this); - enet_host_flush(host); - enet_host_destroy(host); - host = NULL; - if (remove_openconnection) OpenConnectionsDel(this); - } - + void OpenP2P(const char *p2prelay_host, int p2prelay_port, unsigned short num_connections, const void* relaykey, unsigned char relaykey_length, unsigned char num_channels) { ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); - if (host) Close(0, true); + if (host) Close(0); host = enet_host_create(NULL, num_connections, num_channels, 0, 0); if (!host) return; @@ -174,9 +162,26 @@ struct ZL_Server_Impl : ZL_Connection_Impl bs.dataLength = 1 + 2 + relaykey_length; enet_socket_send(host->socket, &P2P->relay_server, &bs, 1); - OpenConnectionsAdd(this); + OpenConnectionsAdd(); + } + + void Close(unsigned int closemsg) + { + if (!host) return; + if (P2P && P2P->doClose) P2P->doClose(this); + for (ENetPeer* it = host->peers; it != &host->peers[host->peerCount]; ++it) + if (it->state == ENET_PEER_STATE_CONNECTED) + enet_peer_disconnect(it, closemsg); + enet_host_flush(host); + enet_host_destroy(host); + host = NULL; + connectionWasClosed = true; } +protected: + ~ZL_Server_Impl() { Close(0); } + virtual void ShutDown() { Close(0); } + static void CloseP2P(ZL_Server_Impl* self) { if (self->P2P->is_master == 1) @@ -195,16 +200,45 @@ struct ZL_Server_Impl : ZL_Connection_Impl self->P2P = NULL; } + virtual bool KeepAlive() + { + if (!ZL_VERIFY(host)) return false; + if (P2P && P2P->doKeepAlive && P2P->doKeepAlive(this)) return true; + ENetEvent event; + while (enet_host_service(host, &event, 0) > 0) + { + ZL_Peer peer = { ENET_NET_TO_HOST_32(event.peer->address.host), event.peer->address.port, event.peer->data, event.peer }; + switch (event.type) + { + case ENET_EVENT_TYPE_RECEIVE: + { + ZL_Packet packet = { event.packet->data, event.packet->dataLength, event.channelID, (event.packet->flags & ENET_PACKET_FLAG_RELIABLE ? ZL_PACKET_RELIABLE : (event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED ? ZL_PACKET_UNSEQUENCED : ZL_PACKET_UNRELIABLE)) }; + sigReceived.call(peer, packet); + } + enet_packet_destroy(event.packet); + break; + case ENET_EVENT_TYPE_CONNECT: + sigConnected.call(peer); //ToDo: include event.data + break; + case ENET_EVENT_TYPE_DISCONNECT: + sigDisconnected.call(peer, event.data); + break; + case ENET_EVENT_TYPE_NONE: + break; + } + } + return true; + } + static bool KeepAliveP2P(ZL_Server_Impl* self) { unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE; - if (enet_socket_wait(self->host->socket, &waitCondition, 0) != 0 || !(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) + if (enet_socket_wait(self->host->socket, &waitCondition, 0) || !(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) { if (ZLSINCE(self->P2P->time_open) >= ENET_PEER_TIMEOUT_MAXIMUM) { - void* DummyData = NULL; - ZL_Peer peer = { SWAPBE32(self->P2P->relay_server.host), self->P2P->relay_server.port, &DummyData, NULL }; - self->sigDisconnected.call(peer); + ZL_Peer peer = { ENET_NET_TO_HOST_32(self->P2P->relay_server.host), self->P2P->relay_server.port, NULL, NULL }; + self->sigDisconnected.call(peer, 0); self->P2P->doKeepAlive = NULL; return false; } @@ -237,36 +271,6 @@ struct ZL_Server_Impl : ZL_Connection_Impl self->P2P->doKeepAlive = NULL; return false; } - - bool KeepAlive() - { - if (!ZL_VERIFY(host)) return false; - if (P2P && P2P->doKeepAlive && P2P->doKeepAlive(this)) return true; - ENetEvent event; - while (enet_host_service(host, &event, 0) > 0) - { - ZL_Peer peer = { SWAPBE32(event.peer->address.host), event.peer->address.port, &event.peer->data, event.peer }; - switch (event.type) - { - case ENET_EVENT_TYPE_RECEIVE: - { - ZL_Packet packet = { event.packet->data, event.packet->dataLength, event.channelID, (event.packet->flags & ENET_PACKET_FLAG_RELIABLE ? ZL_PACKET_RELIABLE : (event.packet->flags & ENET_PACKET_FLAG_UNSEQUENCED ? ZL_PACKET_UNSEQUENCED : ZL_PACKET_UNRELIABLE)) }; - sigReceived.call(peer, packet); - } - enet_packet_destroy(event.packet); - break; - case ENET_EVENT_TYPE_CONNECT: - sigConnected.call(peer); //ToDo: include event.data - break; - case ENET_EVENT_TYPE_DISCONNECT: - sigDisconnected.call(peer); - break; - case ENET_EVENT_TYPE_NONE: - break; - } - } - return true; - } }; ZL_IMPL_OWNER_DEFAULT_IMPLEMENTATIONS(ZL_Server) @@ -281,7 +285,6 @@ ZL_Server::ZL_Server(const char *p2prelay_host, int p2prelay_port, unsigned shor impl->OpenP2P(p2prelay_host, p2prelay_port, max_connections, relaykey, relaykey_length, num_channels); } - void ZL_Server::Open(int server_port, unsigned short max_connections, const char *bind_host, unsigned char num_channels) { if (!impl) impl = new ZL_Server_Impl(); @@ -296,73 +299,45 @@ void ZL_Server::OpenP2P(const char *p2prelay_host, int p2prelay_port, unsigned s void ZL_Server::Close(unsigned int closemsg) { - if (impl && impl->host) impl->Close(closemsg, true); + if (impl && impl->host) impl->Close(closemsg); } -void ZL_Server::Send(ZL_PeerHandle peerhandle, const ZL_Packet& packet) +void ZL_Server::Send(ZL_PeerHandle peer, const void* data, size_t length, ZL_Packet_Reliability type, unsigned char channel) { - if (!impl || !impl->host) return; - enet_peer_send((ENetPeer*)peerhandle, packet.channel, enet_packet_create(packet.data, packet.length, (packet.type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (packet.type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); + if (impl && impl->host) + enet_peer_send((ENetPeer*)peer, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); } -void ZL_Server::Send(ZL_PeerHandle peerhandle, const void* data, size_t length, unsigned char channel, ZL_Packet_Reliability type) +void ZL_Server::Broadcast(const void* data, size_t length, ZL_Packet_Reliability type, unsigned char channel) { - if (!impl || !impl->host) return; - enet_peer_send((ENetPeer*)peerhandle, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); + if (impl && impl->host) + enet_host_broadcast(impl->host, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); } -void ZL_Server::Send(const std::vector& peerhandles, const ZL_Packet& packet) -{ - if (!impl || !impl->host) return; - ENetPacket* enetpacket = enet_packet_create(packet.data, packet.length, (packet.type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (packet.type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED))); - for (std::vector::const_iterator it = peerhandles.begin(); it != peerhandles.end(); ++it) - enet_peer_send((ENetPeer*)(*it), packet.channel, enetpacket); -} - -void ZL_Server::Send(const std::vector& peerhandles, const void* data, size_t length, unsigned char channel, ZL_Packet_Reliability type) +void ZL_Server::Broadcast(const void* data, size_t length, ZL_PeerHandle peer_except, ZL_Packet_Reliability type, unsigned char channel) { if (!impl || !impl->host) return; ENetPacket* enetpacket = enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED))); - for (std::vector::const_iterator it = peerhandles.begin(); it != peerhandles.end(); ++it) - enet_peer_send((ENetPeer*)(*it), channel, enetpacket); -} - -void ZL_Server::Broadcast(const ZL_Packet& packet) -{ - if (!impl || !impl->host) return; - enet_host_broadcast(impl->host, packet.channel, enet_packet_create(packet.data, packet.length, (packet.type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (packet.type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); -} - -void ZL_Server::Broadcast(const ZL_Packet& packet, ZL_PeerHandle peerhandle_except) -{ - if (!impl || !impl->host) return; - ENetPacket* enetpacket = enet_packet_create(packet.data, packet.length, (packet.type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (packet.type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED))); for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) - if (it->state == ENET_PEER_STATE_CONNECTED && it != peerhandle_except) - enet_peer_send(it, packet.channel, enetpacket); + if (it->state == ENET_PEER_STATE_CONNECTED && it != peer_except) + enet_peer_send(it, channel, enetpacket); if (enetpacket->referenceCount == 0) enet_packet_destroy(enetpacket); } -void ZL_Server::Broadcast(const void* data, size_t length, unsigned char channel, ZL_Packet_Reliability type) -{ - if (!impl || !impl->host) return; - enet_host_broadcast(impl->host, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); -} - -void ZL_Server::Broadcast(const void* data, size_t length, ZL_PeerHandle peerhandle_except, unsigned char channel, ZL_Packet_Reliability type) +void ZL_Server::DisconnectPeer(ZL_PeerHandle peerhandle, unsigned int closemsg) { - if (!impl || !impl->host) return; - ENetPacket* enetpacket = enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED))); - for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) - if (it->state == ENET_PEER_STATE_CONNECTED && it != peerhandle_except) - enet_peer_send(it, channel, enetpacket); - if (enetpacket->referenceCount == 0) enet_packet_destroy(enetpacket); + if (impl && impl->host) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) + { + if (it != peerhandle) continue; + if (it->state == ENET_PEER_STATE_CONNECTED) enet_peer_disconnect(it, closemsg); + return; + } } void ZL_Server::GetPeerHandles(std::vector& out_list) { out_list.clear(); - if (impl) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) + if (impl && impl->host) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) if (it->state == ENET_PEER_STATE_CONNECTED) out_list.push_back(&*it); } @@ -370,30 +345,23 @@ void ZL_Server::GetPeerHandles(std::vector& out_list) void ZL_Server::GetPeerDetails(std::vector& out_list) { out_list.clear(); - if (impl) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) + if (impl && impl->host) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) { if (it->state != ENET_PEER_STATE_CONNECTED) continue; - ZL_Peer peer = { SWAPBE32(it->address.host), it->address.port, &it->data, it }; + ZL_Peer peer = { ENET_NET_TO_HOST_32(it->address.host), it->address.port, it->data, it }; out_list.push_back(peer); } } -ZL_Signal_v1& ZL_Server::sigConnected() +void ZL_Server::SetPeerData(ZL_PeerHandle handle, void* data) { - if (!impl) impl = new ZL_Server_Impl(); - return impl->sigConnected; + if (handle) ((ENetPeer*)handle)->data = data; } -ZL_Signal_v1& ZL_Server::sigDisconnected() +void ZL_Server::SetPeerData(const ZL_Peer& peer, void* data) { - if (!impl) impl = new ZL_Server_Impl(); - return impl->sigDisconnected; -} - -ZL_Signal_v2& ZL_Server::sigReceived() -{ - if (!impl) impl = new ZL_Server_Impl(); - return impl->sigReceived; + ((ENetPeer*)peer.handle)->data = data; + ((ZL_Peer*)&peer)->data = data; } bool ZL_Server::IsOpened() @@ -404,9 +372,8 @@ bool ZL_Server::IsOpened() size_t ZL_Server::GetPeerCount() { size_t count = 0; - if (impl) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) - if (it->state == ENET_PEER_STATE_CONNECTED) - count++; + if (impl && impl->host) for (ENetPeer *it = impl->host->peers, *end = &impl->host->peers[impl->host->peerCount]; it != end; ++it) + if (it->state == ENET_PEER_STATE_CONNECTED) count++; return count; } @@ -415,25 +382,25 @@ bool ZL_Server::IsP2PMaster() return (impl && impl->P2P && impl->P2P->is_master == 1); } +ZL_Signal_v1& ZL_Server::sigConnected() { if (!impl) impl = new ZL_Server_Impl(); return impl->sigConnected; } +ZL_Signal_v2& ZL_Server::sigDisconnected() { if (!impl) impl = new ZL_Server_Impl(); return impl->sigDisconnected; } +ZL_Signal_v2& ZL_Server::sigReceived() { if (!impl) impl = new ZL_Server_Impl(); return impl->sigReceived; } + //---------------------------------------------------------------------------------------------------------------------------------------------- struct ZL_Client_Impl : ZL_Connection_Impl { ENetHost *host; ZL_Signal_v0 sigConnected; - ZL_Signal_v0 sigDisconnected; + ZL_Signal_v1 sigDisconnected; ZL_Signal_v1 sigReceived; ZL_Client_Impl() : host(NULL) { } - ~ZL_Client_Impl() { Disconnect(0, true); } - - void ShutDown() { Disconnect(0, false); } - void Connect(const char *connect_host, int port, unsigned char num_channels) { ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); - if (host) Disconnect(0, true); + if (host) Disconnect(0); host = enet_host_create(NULL, 1, num_channels, 0, 0); if (!host) return; @@ -444,20 +411,24 @@ struct ZL_Client_Impl : ZL_Connection_Impl enet_host_connect(host, &address, num_channels, 0); - OpenConnectionsAdd(this); + OpenConnectionsAdd(); } - void Disconnect(unsigned int closemsg, bool remove_openconnection) + void Disconnect(unsigned int closemsg) { if (!host) return; if (host && host->peerCount) enet_peer_disconnect(host->peers, closemsg); if (host) enet_host_flush(host); enet_host_destroy(host); host = NULL; - if (remove_openconnection) OpenConnectionsDel(this); + connectionWasClosed = true; } - bool KeepAlive() +protected: + ~ZL_Client_Impl() { Disconnect(0); } + virtual void ShutDown() { Disconnect(0); } + + virtual bool KeepAlive() { if (!ZL_VERIFY(host)) return false; ENetEvent event; @@ -478,7 +449,7 @@ struct ZL_Client_Impl : ZL_Connection_Impl case ENET_EVENT_TYPE_DISCONNECT: enet_host_destroy(host); host = NULL; - sigDisconnected.call(); + sigDisconnected.call(event.data); return false; case ENET_EVENT_TYPE_NONE: break; @@ -503,37 +474,13 @@ void ZL_Client::Connect(const char *host, int port, unsigned char num_channels) void ZL_Client::Disconnect(unsigned int closemsg) { - if (impl) impl->Disconnect(closemsg, true); -} - -void ZL_Client::Send(ZL_Packet &packet) -{ - if (!impl || !impl->host || ! impl->host->peerCount) return; - enet_peer_send(impl->host->peers, packet.channel, enet_packet_create(packet.data, packet.length, (packet.type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (packet.type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); -} - -void ZL_Client::Send(const void* data, size_t length, unsigned char channel, ZL_Packet_Reliability type) -{ - if (!impl || !impl->host ||!impl->host->peerCount) return; - enet_peer_send(impl->host->peers, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); + if (impl) impl->Disconnect(closemsg); } -ZL_Signal_v0& ZL_Client::sigConnected() +void ZL_Client::Send(const void* data, size_t length, ZL_Packet_Reliability type, unsigned char channel) { - if (!impl) impl = new ZL_Client_Impl(); - return impl->sigConnected; -} - -ZL_Signal_v0& ZL_Client::sigDisconnected() -{ - if (!impl) impl = new ZL_Client_Impl(); - return impl->sigDisconnected; -} - -ZL_Signal_v1& ZL_Client::sigReceived() -{ - if (!impl) impl = new ZL_Client_Impl(); - return impl->sigReceived; + if (impl && impl->host && impl->host->peerCount) + enet_peer_send(impl->host->peers, channel, enet_packet_create(data, length, (type == ZL_PACKET_RELIABLE ? ENET_PACKET_FLAG_RELIABLE : (type == ZL_PACKET_UNRELIABLE ? 0 : ENET_PACKET_FLAG_UNSEQUENCED)))); } bool ZL_Client::IsConnected() @@ -541,6 +488,10 @@ bool ZL_Client::IsConnected() return (impl && impl->host && impl->host->peerCount); } +ZL_Signal_v0& ZL_Client::sigConnected() { if (!impl) impl = new ZL_Client_Impl(); return impl->sigConnected; } +ZL_Signal_v1& ZL_Client::sigDisconnected() { if (!impl) impl = new ZL_Client_Impl(); return impl->sigDisconnected; } +ZL_Signal_v1& ZL_Client::sigReceived() { if (!impl) impl = new ZL_Client_Impl(); return impl->sigReceived; } + //---------------------------------------------------------------------------------------------------------------------------------------------- struct ZL_RawSocket_Impl : ZL_Connection_Impl @@ -551,46 +502,37 @@ struct ZL_RawSocket_Impl : ZL_Connection_Impl ZL_RawSocket_Impl() : socket(0) { } - ~ZL_RawSocket_Impl() - { - OpenConnectionsDel(this); - ShutDown(); - } - - void ShutDown() - { - if (!socket) return; - enet_socket_destroy(socket); - socket = 0; - } - bool ConnectSync(const char *host, int port, ZL_RawSocket::Type type) { - if (socket) OpenConnectionsDel(this); - ShutDown(); - //ZL_LOG2("NET", "Connect to host: %s - Port: %d", host, port); + Close(); this->type = type; ENetAddress address; if (enet_address_set_host(&address, host) != 0) return false; address.port = port; - //ZL_LOG1("NET", " Host resolved address: %x", address.host); socket = enet_socket_create(type == ZL_RawSocket::TYPE_TCP ? ENET_SOCKET_TYPE_STREAM : ENET_SOCKET_TYPE_DATAGRAM); if (!socket) return false; - //ZL_LOG0("NET", " Socket created"); if (enet_socket_connect(socket, &address) != 0) { enet_socket_destroy(socket); socket = 0; return false; } - //ZL_LOG0("NET", " Connected"); - OpenConnectionsAdd(this); + OpenConnectionsAdd(); return true; } - bool KeepAlive() + void Close() + { + if (socket) { enet_socket_destroy(socket); socket = 0; } + connectionWasClosed = true; + } + +protected: + ~ZL_RawSocket_Impl() { Close(); } + void ShutDown() { Close(); } + + virtual bool KeepAlive() { if (!ZL_VERIFY(socket)) return false; while (1) { - unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_SEND; - if (enet_socket_wait(socket, &waitCondition, 0) != 0) return true; - if (!(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) return true; + unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE; + if (enet_socket_wait(socket, &waitCondition, 0) || !(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) return true; char buffer[1024]; ENetBuffer br; @@ -621,8 +563,7 @@ bool ZL_RawSocket::ConnectSync(const char *host, int port, Type type) void ZL_RawSocket::Disconnect() { if (!impl) return; - OpenConnectionsDel(impl); - impl->ShutDown(); + impl->Close(); } int ZL_RawSocket::Send(const void* data, size_t length) @@ -645,59 +586,66 @@ ZL_Signal_v1& ZL_RawSocket::sigReceived() const struct ZL_BasicTCPConnection_Impl : ZL_Connection_Impl { +protected: ENetSocket socket; - bool started, first_keep_alive, cancel; - - ZL_BasicTCPConnection_Impl() : socket(0), started(false), first_keep_alive(true), cancel(false) { } + bool started, first_keep_alive, cancel, thread_active; - ~ZL_BasicTCPConnection_Impl() - { - ShutDown(); - if (started && !first_keep_alive) OpenConnectionsDel(this); - } + ZL_BasicTCPConnection_Impl() : socket(0), started(false) { } + ~ZL_BasicTCPConnection_Impl() { Close(); } - void ShutDown() + void StartConnection() { - cancel = true; - if (!socket) return; - enet_socket_destroy(socket); - socket = 0; - } - - void Connect() - { - if (started) return; - started = true; + Close(); + cancel = false; + started = thread_active = first_keep_alive = true; + OpenConnectionsAdd(); AddRef(); ZL_CreateThread(&ConnectRunThread, (void*)this); } + + void Close() + { + if (!started) return; + if (thread_active) + { + cancel = true; + while (thread_active) { ZL_Delay(1); } + cancel = false; + DelRef(); + } + if (socket) + { + enet_socket_destroy(socket); + socket = 0; + } + started = false; + connectionWasClosed = true; + } virtual bool GetHost(ZL_String& host, int& port) = 0; - + static void* ConnectRunThread(void* vimpl) { ZL_BasicTCPConnection_Impl* impl = (ZL_BasicTCPConnection_Impl*)vimpl; ENetSocket tmpsocket = 0; ENetAddress address; - ZL_String host; int port; if (!impl->GetHost(host, port)) goto done; + ZL_String host; int port; + bool aborted = !impl->GetHost(host, port); + address.port = (unsigned short)port; //ZL_LOG2("NET", "Connect to host: %s - Port: %d", host.c_str(), port); - if (impl->cancel || impl->GetRefCount() == 1 || enet_address_set_host(&address, host.c_str()) != 0) goto done; + if (impl->cancel || impl->GetRefCount() == 1 || aborted || enet_address_set_host(&address, host.c_str()) != 0) aborted = true; - address.port = port; //ZL_LOG1("NET", " Host resolved address: %x", address.host); - if (impl->cancel || impl->GetRefCount() == 1 || (!(tmpsocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM)))) goto done; + else if (impl->cancel || impl->GetRefCount() == 1 || (!(tmpsocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM)))) aborted = true; //ZL_LOG0("NET", " Socket created"); - if (impl->cancel || impl->GetRefCount() == 1 || enet_socket_connect(tmpsocket, &address) != 0) goto done; - - impl->socket = tmpsocket; - tmpsocket = 0; + else if (impl->cancel || impl->GetRefCount() == 1 || enet_socket_connect(tmpsocket, &address) != 0) aborted = true; - done: - if (tmpsocket) enet_socket_destroy(tmpsocket); - if (impl->cancel) { impl->DelRef(); return NULL; } - else { OpenConnectionsAdd(impl); return NULL; } + if (aborted && tmpsocket) { enet_socket_destroy(tmpsocket); } + else impl->socket = tmpsocket; + impl->thread_active = false; + return NULL; } static bool SplitUrl(const ZL_String& url, ZL_String::size_type protocol_name_length, ZL_String* out_path = NULL, ZL_String* out_host = NULL, int* out_port = NULL) @@ -724,13 +672,8 @@ struct ZL_BasicTCPConnection_Impl : ZL_Connection_Impl return true; } - char* GetHTTPContentStart(char* buffer, size_t length) - { - for (char *content = buffer, *end = buffer + length; content < end-2; content++) - if ((content[0]=='\n'&&content[1]=='\n') || (content < end-4 && content[0]=='\r'&&content[1]=='\n'&&content[2]=='\r'&&content[3]=='\n')) - return content + (content[0]=='\n' ? 2 : 4); - return NULL; - } +private: + virtual void ShutDown() { Close(); } }; struct ZL_HttpConnection_Impl : ZL_BasicTCPConnection_Impl @@ -746,21 +689,29 @@ struct ZL_HttpConnection_Impl : ZL_BasicTCPConnection_Impl ZL_HttpConnection_Impl() : timeout_msec(10000), dostream(false), http_status(0) { } + void Connect(const char* url) + { + this->url = url; + StartConnection(); + } + +protected: virtual bool GetHost(ZL_String& host, int& port) { return SplitUrl(url, 4, NULL, &host, &port); //only support "http" protocol } - bool KeepAlive() + virtual bool KeepAlive() { if (first_keep_alive) { - bool done = (GetRefCount() == 1); + if (thread_active) return true; + if (GetRefCount() == 1) { Close(); delete this; return false; } DelRef(); if (socket) { ZL_String header, path, host; - if (done || !SplitUrl(url, 4, &path, &host)) return false; //only support "http" protocol + if (!SplitUrl(url, 4, &path, &host)) return false; //only support "http" protocol first_keep_alive = false; ENetBuffer bs; @@ -786,13 +737,13 @@ struct ZL_HttpConnection_Impl : ZL_BasicTCPConnection_Impl { sigReceivedString.call(-1, ZL_String::EmptyString); sigReceivedData.call(-1, NULL, 0); - ShutDown(); + Close(); return false; } - while (1) + for (;;) { - unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_SEND; - if (enet_socket_wait(socket, &waitCondition, 0) != 0) return true; + unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE; + if (enet_socket_wait(socket, &waitCondition, 0)) return true; if ((!(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) && (!timeout_msec || ZL_Application::Ticks < timeout_tick)) return true; unsigned char buffer[1024]; @@ -826,7 +777,7 @@ struct ZL_HttpConnection_Impl : ZL_BasicTCPConnection_Impl } else { - char* content = NULL, *end = (data.empty() ? NULL : &data[0]+data.size()), *status = NULL; + const char* content = NULL, *end = (data.empty() ? NULL : &data[0]+data.size()), *status = NULL; if (!dostream && data.size()) { content = GetHTTPContentStart(&data[0], data.size()); @@ -841,27 +792,334 @@ struct ZL_HttpConnection_Impl : ZL_BasicTCPConnection_Impl sigReceivedData.call(http_status, content, data_size); data.clear(); - ShutDown(); + Close(); return false; } } } }; -struct ZL_WebSocketConnection_Impl : ZL_BasicTCPConnection_Impl +struct ZL_WebSocket +{ + enum { OPCODE_CONTINUATION = 0, OPCODE_TEXT = 1, OPCODE_BINARY = 2, OPCODE_CONNECTIONCLOSE = 8, OPCODE_PING = 9, OPCODE_PONG = 10, _OPCODE_BITMASK = 0xF }; + + static size_t SocketRecieve(ENetSocket sock, char*& buffer, size_t& bufferSize) + { + if (!buffer) buffer = (char*)malloc((bufferSize = 1024)); + ENetBuffer br; + br.data = buffer; + br.dataLength = bufferSize; + size_t rec = 0; + for (int r; (r = enet_socket_receive(sock, NULL, &br, 1)) > 0;) + { + rec += r; + if ((size_t)r != br.dataLength) break; + buffer = (char*)realloc(buffer, (bufferSize += 1024)); + br.data = buffer + rec; + br.dataLength = 1024; + } + return rec; + } + + static bool Read(char*& cursor, size_t& len, size_t& remain, size_t& opcode) + { + if (remain < 2 || ((cursor[0] & 0xF0) != 0x80)) return false; //need FIN 0x80 flag as fragmented frames are not supported + size_t headerlen = 2 + ((cursor[1]&127)==127 ? 8 : ((cursor[1]&127)==126 ? 2 : 0)); + if (remain < headerlen) return false; + len = (headerlen == 2 ? cursor[1]&127 : (headerlen == 4 ? ENET_NET_TO_HOST_16(*(unsigned short*)(cursor+2)) : ENET_NET_TO_HOST_32(*(unsigned int*)(cursor+6)))); + if (remain < headerlen+len) return false; + bool hasMask = !!(cursor[1] & 0x80); + opcode = (cursor[0] & _OPCODE_BITMASK); + remain -= headerlen+len; + cursor += headerlen; + if (hasMask) + { + if (remain < 4) return false; + char* mask = cursor; cursor += 4; remain -= 4; + if (mask[0] || mask[1] || mask[2] || mask[3]) for (size_t i = 0; i != len; i++) cursor[i] ^= mask[i&3]; + } + //ZL_LOG("WEBSOCK", "[RECV] OP: %d [BINARY] LEN: %d [%02x %02x %02x %02x ...]", opcode, (int)len, ((char*)cursor)[0], ((char*)cursor)[1], ((char*)cursor)[2], ((char*)cursor)[3]); + return true; + } + + static void SendOp(ENetSocket sock, unsigned char opcode, const void* buf, size_t len, bool mask) + { + if (!sock) return; + unsigned char header[2 + 8 + 4] = { (unsigned char)(0x80 | opcode), (unsigned char)(mask ? 0x80 : 0) }, headerlen = 2; + if (len > 0xFFFF) { headerlen += 8; header[1] |= 127; ((unsigned int*)(header+2))[0] = 0; ((unsigned int*)(header+2))[1] = ENET_HOST_TO_NET_32((unsigned int)len); } + else if (len > 125) { headerlen += 2; header[1] |= 126; ((unsigned short*)header)[1] = ENET_HOST_TO_NET_16((unsigned short)len); } + else header[1] |= len; + if (mask) { *(unsigned int*)(header+headerlen) = 0; headerlen += 4; } + ENetBuffer bs[2]; + bs[0].data = (void*)header; bs[0].dataLength = headerlen; + bs[1].data = (void*)buf; bs[1].dataLength = (unsigned int)len; + //ZL_LOG("WEBSOCK", "[SEND] OP: %d [BINARY] LEN: %d [%02x %02x %02x %02x ...]", opcode, (int)len, ((char*)buf)[0], ((char*)buf)[1], ((char*)buf)[2], ((char*)buf)[3]); + enet_socket_send(sock, NULL, bs, 2); + } +}; + +struct ZL_WebSocketServer_Impl : ZL_Connection_Impl, ZL_WebSocket +{ + ENetSocket listenSocket; + ZL_Peer* peers; + int numPeers; + enet_uint8 packetData[ENET_PROTOCOL_MAXIMUM_MTU]; + char *buffer; + size_t bufferSize; + #define WEBSOCK_NO_HANDSHAKE_DATA ((void**)(this)) + + ZL_Signal_v2 sigConnected; + ZL_Signal_v2 sigDisconnected; + ZL_Signal_v2 sigReceivedText; + ZL_Signal_v3 sigReceivedBinary; + + ZL_WebSocketServer_Impl() : peers(NULL), numPeers(0), buffer(NULL) { } + + inline bool IsFullyConnected(ZL_Peer* peer) { return (peer->host && peer->data != WEBSOCK_NO_HANDSHAKE_DATA); } + + void Open(int server_port, unsigned short max_connections, const char *bind_host) + { + ZL_ASSERTMSG(OpenConnections, "ZL_Network::Init needs to be called before using network functions"); + + listenSocket = enet_socket_create(ENET_SOCKET_TYPE_STREAM); + enet_socket_set_option(listenSocket, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(listenSocket, ENET_SOCKOPT_NODELAY, 1); + ENetAddress address; + if (bind_host) enet_address_set_host(&address, bind_host); + else address.host = ENET_HOST_ANY; + address.port = server_port; + if (!max_connections || listenSocket == ENET_SOCKET_NULL || enet_socket_bind(listenSocket, &address) < 0 || enet_socket_listen(listenSocket, max_connections) < 0) + { + enet_socket_destroy(listenSocket); + return; + } + + numPeers = max_connections; + peers = (ZL_Peer*)malloc(sizeof(ZL_Peer) * numPeers); + memset(peers, 0, sizeof(ZL_Peer) * numPeers); + + OpenConnectionsAdd(); + } + + void Close(short code = 1001) //1001: Going Away + { + if (!peers) return; + code = ENET_HOST_TO_NET_16(code); + for (ZL_Peer* peer = peers, *peerEnd = &peers[numPeers]; peer != peerEnd; peer++) + { + if (!peer->host) continue; + SendOp((ENetSocket)(size_t)peer->handle, OPCODE_CONNECTIONCLOSE, &code, 2, false); + enet_socket_destroy((ENetSocket)(size_t)peer->handle); + } + enet_socket_destroy(listenSocket); + free(peers); + peers = NULL; + numPeers = 0; + if (buffer) { free(buffer); buffer = NULL; } + connectionWasClosed = true; + } + +protected: + ~ZL_WebSocketServer_Impl() { Close(); } + virtual void ShutDown() { Close(1001); } + + virtual bool KeepAlive() + { + ENetAddress address; + for (ENetSocket newClient; (newClient = enet_socket_accept(listenSocket, &address)) != ENET_SOCKET_NULL; newClient = ENET_SOCKET_NULL) + { + ZL_Peer* peer = peers, *peerEnd = &peers[numPeers]; + for (; peer != peerEnd; ++peer) if (!peer->host) break; + if (peer == peerEnd) enet_socket_destroy(newClient); + else + { + enet_socket_set_option(newClient, ENET_SOCKOPT_NONBLOCK, 1); + enet_socket_set_option(newClient, ENET_SOCKOPT_NODELAY, 1); + peer->host = ENET_NET_TO_HOST_32(address.host); + peer->port = address.port; + *const_cast(&peer->handle) = (void*)(size_t)newClient; + peer->data = WEBSOCK_NO_HANDSHAKE_DATA; + } + } + for (ZL_Peer* peer = peers, *peerEnd = &peers[numPeers]; peer != peerEnd;) + { + unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE; + if (!peer->host || enet_socket_wait((ENetSocket)(size_t)peer->handle, &waitCondition, 0) || !(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) + { + peer++; + continue; + } + + size_t rec = SocketRecieve((ENetSocket)(size_t)peer->handle, buffer, bufferSize); + if (rec < 2) + { + if (peer->data != WEBSOCK_NO_HANDSHAKE_DATA) sigDisconnected.call(*peer, 1006); //1006: Abnormal closure + WEBSOCKETSERVERSHUTDOWNPEER: + enet_socket_destroy((ENetSocket)(size_t)peer->handle); + memset(peer, 0, sizeof(ZL_Peer)); + peer++; + continue; + } + + char* cursor = buffer; + if (peer->data == WEBSOCK_NO_HANDSHAKE_DATA) + { + char* content = GetHTTPContentStart(buffer, rec); + if (content) content[-1] = '\0'; //string terminator for strstr/strchr + const char* getMethod = (content ? strstr(buffer, "GET ") : NULL); + const char* getPathEnd = (getMethod == buffer ? strchr(getMethod+4, ' ') : NULL); + const char* webSocketKey = (getPathEnd ? strstr(getPathEnd, "Sec-WebSocket-Key: ") : NULL); + const char* webSocketKeyEnd = (webSocketKey ? strchr(webSocketKey, '\n') : NULL); + if (!webSocketKeyEnd) goto WEBSOCKETSERVERSHUTDOWNPEER; + + webSocketKey += sizeof("Sec-WebSocket-Key: ")-1; + if (webSocketKeyEnd[-1] == '\r') webSocketKeyEnd--; + + ZL_String requestKey(webSocketKey, webSocketKeyEnd - webSocketKey), header; + requestKey << "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + unsigned char acceptKey[20]; + ZL_Checksum::SHA1(requestKey.c_str(), requestKey.size(), acceptKey); + header << "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " << ZL_Base64::Encode(acceptKey, 20) << "\r\n\r\n"; + + ENetBuffer bs; + bs.data = (void*)header.c_str(); bs.dataLength = header.size(); + if (enet_socket_send((ENetSocket)(size_t)peer->handle, NULL, &bs, 1) <= 0) goto WEBSOCKETSERVERSHUTDOWNPEER; + + peer->data = NULL; + sigConnected.call(*peer, ZL_String(getMethod+4, getPathEnd-getMethod-4)); + rec -= (content - buffer); + cursor = content; + } + + for (size_t len, opcode; Read(cursor, len, rec, opcode); cursor += len) + { + switch (opcode) + { + case OPCODE_TEXT: if (sigReceivedText.HasConnections()) sigReceivedText.call(*peer, ZL_String(cursor, len)); break; + case OPCODE_BINARY: if (sigReceivedBinary.HasConnections()) sigReceivedBinary.call(*peer, cursor, len); break; + case OPCODE_PING: SendOp((ENetSocket)(size_t)peer->handle, OPCODE_PONG, cursor, len, false); break; + case OPCODE_CONNECTIONCLOSE: sigDisconnected.call(*peer, (len >= 2 ? ENET_NET_TO_HOST_16(*(unsigned short*)cursor) : 0)); goto WEBSOCKETSERVERSHUTDOWNPEER; + } + } + } + return true; + } +}; + +ZL_IMPL_OWNER_DEFAULT_IMPLEMENTATIONS(ZL_WebSocketServer) + +ZL_WebSocketServer::ZL_WebSocketServer(int server_port, unsigned short max_connections, const char *bind_host) : impl(NULL) +{ + Open(server_port, max_connections, bind_host); +} + +void ZL_WebSocketServer::Open(int server_port, unsigned short max_connections, const char *bind_host) +{ + if (!impl) impl = new struct ZL_WebSocketServer_Impl(); + else impl->Close(); + impl->Open(server_port, max_connections, bind_host); +} + +void ZL_WebSocketServer::Close(short code) +{ + if (impl) impl->Close(code); +} + +void ZL_WebSocketServer::Send(ZL_PeerHandle peer, const void* data, size_t length, bool is_text) +{ + if (impl) ZL_WebSocket::SendOp((ENetSocket)(size_t)peer, (is_text ? ZL_WebSocket::OPCODE_TEXT : ZL_WebSocket::OPCODE_BINARY), data, length, false); +} + +void ZL_WebSocketServer::Broadcast(const void* data, size_t length, bool is_text) +{ + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (impl->IsFullyConnected(p)) ZL_WebSocket::SendOp((ENetSocket)(size_t)p->handle, (is_text ? ZL_WebSocket::OPCODE_TEXT : ZL_WebSocket::OPCODE_BINARY), data, length, false); +} + +void ZL_WebSocketServer::Broadcast(const void* data, size_t length, ZL_PeerHandle except, bool is_text) +{ + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (impl->IsFullyConnected(p) && p->handle != except) ZL_WebSocket::SendOp((ENetSocket)(size_t)p->handle, (is_text ? ZL_WebSocket::OPCODE_TEXT : ZL_WebSocket::OPCODE_BINARY), data, length, false); +} + +void ZL_WebSocketServer::DisconnectPeer(ZL_PeerHandle peerhandle, short code) +{ + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + { + if (p->handle != peerhandle) continue; + code = ENET_HOST_TO_NET_16(code); + ZL_WebSocket::SendOp((ENetSocket)(size_t)p->handle, ZL_WebSocket::OPCODE_CONNECTIONCLOSE, &code, 2, false); + enet_socket_destroy((ENetSocket)(size_t)p->handle); + memset(p, 0, sizeof(ZL_Peer)); + return; + } +} + +void ZL_WebSocketServer::GetPeerHandles(std::vector& out_list) +{ + out_list.clear(); + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (impl->IsFullyConnected(p)) out_list.push_back(p->handle); +} + +void ZL_WebSocketServer::GetPeerDetails(std::vector& out_list) +{ + out_list.clear(); + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (impl->IsFullyConnected(p)) out_list.push_back(*p); +} + +void ZL_WebSocketServer::SetPeerData(ZL_PeerHandle handle, void* data) +{ + if (impl) for (ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (p->handle == handle) { p->data = data; return; } +} + +void ZL_WebSocketServer::SetPeerData(const ZL_Peer& peer, void* data) +{ + ((ZL_Peer*)&peer)->data = data; +} + +bool ZL_WebSocketServer::IsOpened() +{ + return (impl && impl->peers); +} + +size_t ZL_WebSocketServer::GetPeerCount() +{ + size_t res = 0; + if (impl) for (const ZL_Peer *p = impl->peers, *pEnd = p + impl->numPeers; p != pEnd; p++) + if (p->host) res++; + return res; +} + +ZL_Signal_v2& ZL_WebSocketServer::sigConnected() { if (!impl) impl = new ZL_WebSocketServer_Impl(); return impl->sigConnected; } +ZL_Signal_v2& ZL_WebSocketServer::sigDisconnected() { if (!impl) impl = new ZL_WebSocketServer_Impl(); return impl->sigDisconnected; } +ZL_Signal_v2& ZL_WebSocketServer::sigReceivedText() { if (!impl) impl = new ZL_WebSocketServer_Impl(); return impl->sigReceivedText; } +ZL_Signal_v3& ZL_WebSocketServer::sigReceivedBinary() { if (!impl) impl = new ZL_WebSocketServer_Impl(); return impl->sigReceivedBinary; } + +struct ZL_WebSocketClient_Impl : ZL_BasicTCPConnection_Impl, ZL_WebSocket { ZL_String url; bool websocket_active; - ZL_Signal_v1 sigReceivedText; - ZL_Signal_v2 sigReceivedBinary; ZL_Signal_v0 sigConnected; - ZL_Signal_v0 sigDisconnected; + ZL_Signal_v1 sigDisconnected; + ZL_Signal_v1 sigReceivedText; + ZL_Signal_v2 sigReceivedBinary; + char *buffer; + size_t bufferSize; - ZL_WebSocketConnection_Impl() : websocket_active(false) { } - - virtual bool GetHost(ZL_String& host, int& port) + ZL_WebSocketClient_Impl() : websocket_active(false), buffer(NULL) { } + + ~ZL_WebSocketClient_Impl() { - return SplitUrl(url, 2, NULL, &host, &port); //only support "ws" protocol + if (buffer) free(buffer); + } + + void Connect(const char* url) + { + this->url = url; + StartConnection(); } void Disconnect(unsigned short code, const char* buf, size_t len) @@ -870,23 +1128,33 @@ struct ZL_WebSocketConnection_Impl : ZL_BasicTCPConnection_Impl if (socket && len) { unsigned short* sendbuf = (unsigned short*)malloc(2 + len); sendbuf[0] = code; memcpy(sendbuf+1, buf, len); - Send(OPCODE_CONNECTIONCLOSE, sendbuf, 2 + len); + SendOp(socket, OPCODE_CONNECTIONCLOSE, sendbuf, 2 + len, true); free(sendbuf); } - else if (socket) Send(OPCODE_CONNECTIONCLOSE, &code, 2); - ShutDown(); + else if (socket) SendOp(socket, OPCODE_CONNECTIONCLOSE, &code, 2, true); + websocket_active = false; + Close(); } - bool KeepAlive() + void Send(const void* buf, size_t len, bool is_text) { SendOp(socket, (is_text ? OPCODE_TEXT : OPCODE_BINARY), buf, len, true); } + +protected: + virtual bool GetHost(ZL_String& host, int& port) + { + return SplitUrl(url, 2, NULL, &host, &port); //only support "ws" protocol + } + + virtual bool KeepAlive() { if (first_keep_alive) { - bool done = (GetRefCount() == 1); + if (thread_active) return true; + if (GetRefCount() == 1) { Close(); delete this; return false; } DelRef(); if (socket) { ZL_String header, path, host; - if (done || !SplitUrl(url, 2, &path, &host)) return false; //only support "ws" protocol + if (!SplitUrl(url, 2, &path, &host)) return false; //only support "ws" protocol first_keep_alive = false; ENetBuffer bs; @@ -898,88 +1166,48 @@ struct ZL_WebSocketConnection_Impl : ZL_BasicTCPConnection_Impl } if (!socket) { - sigDisconnected.call(); - ShutDown(); + WEBSOCKETCLIENTABORTED: + sigDisconnected.call(1006); //1006: Abnormal closure + WEBSOCKETCLIENTCLOSED: + websocket_active = false; + Close(); return false; } - while (1) + for (;;) { - unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE | ENET_SOCKET_WAIT_SEND; - if (enet_socket_wait(socket, &waitCondition, 0) != 0) return true; - if (!(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) return true; + unsigned int waitCondition = ENET_SOCKET_WAIT_RECEIVE; + if (enet_socket_wait(socket, &waitCondition, 0) || !(waitCondition & ENET_SOCKET_WAIT_RECEIVE)) return true; - char bufferstack[1024], *bufferheap = NULL, *buffer = bufferstack; - ENetBuffer br; - br.data = buffer; - br.dataLength = 1024; - size_t rec = 0; - for (int r; (r = enet_socket_receive(socket, NULL, &br, 1)) > 0;) + size_t rec = SocketRecieve(socket, buffer, bufferSize); + if (rec < 2) goto WEBSOCKETCLIENTABORTED; + + char* cursor = buffer; + if (!websocket_active) { - rec += r; - if (r != 1024) break; - buffer = bufferheap = (char*)realloc(bufferheap, rec + 1024); - if (rec == 1024) memcpy(bufferheap, bufferstack, 1024); - br.data = bufferheap + rec; + char* content = GetHTTPContentStart(buffer, rec); + if (content) content[-1] = '\0'; //string terminator for strstr/strchr + const char* protocol = (content ? strstr(buffer, "HTTP") : NULL); + const char* protocolEnd = (protocol == buffer ? strchr(protocol+4, ' ') : NULL); + const char* status101 = (protocolEnd ? strstr(protocolEnd, " 101 ") : NULL); + if (!status101 || status101 != protocolEnd) goto WEBSOCKETCLIENTABORTED; + websocket_active = true; + sigConnected.call(); + rec -= content - buffer; + cursor = content; } - if (rec < 2) websocket_active = false; - else + + for (size_t len, opcode; websocket_active && Read(cursor, len, rec, opcode); cursor += len) { - if (!websocket_active) + switch (opcode) { - char *status = NULL; - for (status = (char*)buffer; status < (char*)buffer+rec-2; status++) if (*status == ' ') break; - int http_status = (status < (char*)buffer+rec-2 ? atoi(status+1) : 0); - if (http_status == 101) - { - websocket_active = true; - sigConnected.call(); - if ((status = GetHTTPContentStart(buffer, rec)) != NULL) { rec -= status - buffer; buffer = status; } - else rec = 2; //ignore rest - } - } - while (websocket_active && rec >= 2) - { - unsigned char opcode = (buffer[0] & _OPCODE_BITMASK); - size_t headerlen = 2 + ((buffer[1]&127)==127 ? 8 : ((buffer[1]&127)==126 ? 2 : 0)); - if (rec < headerlen) break; - size_t len = (headerlen == 2 ? buffer[1]&127 : (headerlen == 4 ? ENET_NET_TO_HOST_16(*(const short*)(buffer+2)) : ENET_NET_TO_HOST_32(*(unsigned int*)(buffer+6)))); - if (rec < headerlen+len) break; - switch (opcode) - { - case OPCODE_TEXT: if (sigReceivedText.HasConnections()) sigReceivedText.call(ZL_String((char*)buffer+headerlen, len)); break; - case OPCODE_BINARY: if (sigReceivedBinary.HasConnections()) sigReceivedBinary.call((char*)buffer+headerlen, len); break; - case OPCODE_PING: Send(OPCODE_PONG, buffer+headerlen, len); break; - case OPCODE_CONNECTIONCLOSE: websocket_active = false; break; - } - rec -= headerlen+len; - buffer += headerlen+len; + case OPCODE_TEXT: if (sigReceivedText.HasConnections()) sigReceivedText.call(ZL_String(cursor, len)); break; + case OPCODE_BINARY: if (sigReceivedBinary.HasConnections()) sigReceivedBinary.call(cursor, len); break; + case OPCODE_PING: SendOp(socket, OPCODE_PONG, cursor, len, true); break; + case OPCODE_CONNECTIONCLOSE: sigDisconnected.call(len >= 2 ? ENET_NET_TO_HOST_16(*(unsigned short*)cursor) : 0); goto WEBSOCKETCLIENTCLOSED; } } - if (bufferheap) free(bufferheap); - if (!websocket_active) { ShutDown(); return KeepAlive(); } //abort } } - - enum { OPCODE_CONTINUATION = 0, OPCODE_TEXT = 1, OPCODE_BINARY = 2, OPCODE_CONNECTIONCLOSE = 8, OPCODE_PING = 9, OPCODE_PONG = 10, _OPCODE_BITMASK = 0xF }; - - void Send(unsigned char opcode, const void* buf, size_t len) - { - if (!socket) return; - unsigned char header[2 + 8 + 4] = { (unsigned char)(0x80 | opcode), 0x80 }, headerlen = 2; - if (len > 0xFFFF) { headerlen += 8; header[1] |= 127; ((unsigned int*)(header+2))[0] = 0; ((unsigned int*)(header+2))[1] = ENET_HOST_TO_NET_32((unsigned int)len); } - else if (len > 125) { headerlen += 2; header[1] |= 126; ((unsigned short*)header)[1] = ENET_HOST_TO_NET_16((unsigned short)len); } - else header[1] |= len; - *(unsigned int*)(header+headerlen) = 0; headerlen += 4; //mask - ENetBuffer bs; - bs.data = malloc(bs.dataLength = headerlen + len); - memcpy(bs.data, header, headerlen); - memcpy((unsigned char*)bs.data+headerlen, buf, len); - enet_socket_send(socket, NULL, &bs, 1); - free(bs.data); - } - - void SendText(const char* buf, size_t len) { Send(ZL_WebSocketConnection_Impl::OPCODE_TEXT, buf, len); } - void SendBinary(const void* buf, size_t len) { Send(ZL_WebSocketConnection_Impl::OPCODE_BINARY, buf, len); } }; #else //ZL_USE_ENET @@ -987,37 +1215,29 @@ struct ZL_WebSocketConnection_Impl : ZL_BasicTCPConnection_Impl bool ZL_Network::Init() { return true; } void ZL_Network::DeInit() { } +ZL_WEBSOCKETCLIENT_IMPL_INTERFACE ZL_HTTPCONNECTION_IMPL_INTERFACE -ZL_WEBSOCKETCONNECTION_IMPL_INTERFACE #endif //ZL_USE_ENET -ZL_IMPL_OWNER_NONULLCON_IMPLEMENTATIONS(ZL_HttpConnection) -ZL_HttpConnection::ZL_HttpConnection() : impl(new ZL_HttpConnection_Impl()) { } -ZL_HttpConnection::ZL_HttpConnection(const char *url) : impl(new ZL_HttpConnection_Impl()) { SetURL(url); } -ZL_HttpConnection& ZL_HttpConnection::SetURL(const char *url) { impl->url = url; return *this; } +ZL_IMPL_OWNER_DEFAULT_IMPLEMENTATIONS(ZL_WebSocketClient) +ZL_WebSocketClient::ZL_WebSocketClient(const char *url) : impl(NULL) { Connect(url); } +void ZL_WebSocketClient::Connect(const char *url) { if (!url) return; if (!impl) impl = new ZL_WebSocketClient_Impl(); impl->Connect(url); } +void ZL_WebSocketClient::Disconnect(unsigned short code, const char *reason, size_t reason_length) const { if (impl) impl->Disconnect(code, reason, reason_length); } +bool ZL_WebSocketClient::IsConnected() const { return (impl && impl->websocket_active); } +void ZL_WebSocketClient::Send(const void* data, size_t length, bool is_text) { if (IsConnected()) impl->Send(data, length, is_text); } +ZL_Signal_v0& ZL_WebSocketClient::sigConnected() { if (!impl) impl = new ZL_WebSocketClient_Impl(); return impl->sigConnected; } +ZL_Signal_v1& ZL_WebSocketClient::sigDisconnected() { if (!impl) impl = new ZL_WebSocketClient_Impl(); return impl->sigDisconnected; } +ZL_Signal_v1& ZL_WebSocketClient::sigReceivedText() { if (!impl) impl = new ZL_WebSocketClient_Impl(); return impl->sigReceivedText; } +ZL_Signal_v2& ZL_WebSocketClient::sigReceivedBinary() { if (!impl) impl = new ZL_WebSocketClient_Impl(); return impl->sigReceivedBinary; } + +ZL_IMPL_OWNER_DEFAULT_IMPLEMENTATIONS(ZL_HttpConnection) +ZL_HttpConnection::ZL_HttpConnection(const char *url) : impl(NULL) { Connect(url); } +ZL_HttpConnection& ZL_HttpConnection::ClearPostData() { if (impl) impl->post_data.clear(); return *this; } ZL_HttpConnection& ZL_HttpConnection::SetPostData(const char *data) { return SetPostData(data, strlen(data)); } -ZL_HttpConnection& ZL_HttpConnection::SetPostData(const void* data, size_t length) { impl->post_data.resize(length); memcpy(&impl->post_data[0], data, length); return *this; } -#ifndef ZL_NO_SOCKETS -ZL_HttpConnection& ZL_HttpConnection::SetTimeout(unsigned int timeout_msec) { impl->timeout_msec = timeout_msec; return *this; } -#endif -ZL_HttpConnection& ZL_HttpConnection::SetDoStreamData(bool DoStreamData) { impl->dostream = DoStreamData; return *this; } -void ZL_HttpConnection::Connect() const { if (impl->url.length()) impl->Connect(); } -ZL_Signal_v2& ZL_HttpConnection::sigReceivedString() { return impl->sigReceivedString; } -ZL_Signal_v3& ZL_HttpConnection::sigReceivedData() { return impl->sigReceivedData; } - -ZL_IMPL_OWNER_NONULLCON_IMPLEMENTATIONS(ZL_WebSocketConnection) -ZL_WebSocketConnection::ZL_WebSocketConnection() : impl(new ZL_WebSocketConnection_Impl) { } -ZL_WebSocketConnection::ZL_WebSocketConnection(const char *url) : impl(new ZL_WebSocketConnection_Impl()) { SetURL(url); } -ZL_WebSocketConnection& ZL_WebSocketConnection::SetURL(const char *url) { impl->url = url; return *this; } -ZL_WebSocketConnection& ZL_WebSocketConnection::SendText(const char *data) { impl->SendText(data, strlen(data)); return *this; } -ZL_WebSocketConnection& ZL_WebSocketConnection::SendText(const char *data, size_t length) { impl->SendText(data, length); return *this; } -ZL_WebSocketConnection& ZL_WebSocketConnection::SendText(const ZL_String& str) { impl->SendText(str.c_str(), str.length()); return *this; } -ZL_WebSocketConnection& ZL_WebSocketConnection::SendBinary(const void* data, size_t length) { impl->SendBinary(data, length); return *this; } -void ZL_WebSocketConnection::Connect() const { if (impl->url.length()) impl->Connect(); } -void ZL_WebSocketConnection::Disconnect(unsigned short code, const char *reason, size_t reason_length) const { impl->Disconnect(code, reason, reason_length); } -bool ZL_WebSocketConnection::IsConnected() const { return impl->websocket_active; } -ZL_Signal_v1& ZL_WebSocketConnection::sigReceivedText() { return impl->sigReceivedText; } -ZL_Signal_v2& ZL_WebSocketConnection::sigReceivedBinary() { return impl->sigReceivedBinary; } -ZL_Signal_v0& ZL_WebSocketConnection::sigConnected() { return impl->sigConnected; } -ZL_Signal_v0& ZL_WebSocketConnection::sigDisconnected() { return impl->sigDisconnected; } +ZL_HttpConnection& ZL_HttpConnection::SetPostData(const void* data, size_t length) { if (!impl) impl = new ZL_HttpConnection_Impl(); impl->post_data.resize(length); memcpy(&impl->post_data[0], data, length); return *this; } +ZL_HttpConnection& ZL_HttpConnection::SetTimeout(unsigned int timeout_msec) { if (!impl) impl = new ZL_HttpConnection_Impl(); impl->timeout_msec = timeout_msec; return *this; } +ZL_HttpConnection& ZL_HttpConnection::SetDoStreamData(bool DoStreamData) { if (!impl) impl = new ZL_HttpConnection_Impl(); impl->dostream = DoStreamData; return *this; } +void ZL_HttpConnection::Connect(const char *url) { if (!url) return; if (!impl) impl = new ZL_HttpConnection_Impl(); impl->Connect(url); } +ZL_Signal_v2& ZL_HttpConnection::sigReceivedString() { if (!impl) impl = new ZL_HttpConnection_Impl(); return impl->sigReceivedString; } +ZL_Signal_v3& ZL_HttpConnection::sigReceivedData() { if (!impl) impl = new ZL_HttpConnection_Impl(); return impl->sigReceivedData; } diff --git a/Source/ZL_Platform.h b/Source/ZL_Platform.h index 26acad2..a2f152e 100644 --- a/Source/ZL_Platform.h +++ b/Source/ZL_Platform.h @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -79,18 +79,19 @@ bool ZL_AudioOpen(); #define ZL_HTTPCONNECTION_PLATFORM #endif #define ZL_HTTPCONNECTION_IMPL_INTERFACE struct ZL_HttpConnection_Impl : ZL_Impl { \ - ZL_String url; std::vector post_data; unsigned int timeout_msec; bool dostream; \ + std::vector post_data; unsigned int timeout_msec; bool dostream; \ ZL_Signal_v2 sigReceivedString; \ ZL_Signal_v3 sigReceivedData; \ - ZL_HttpConnection_Impl(); void Connect(); ZL_HTTPCONNECTION_PLATFORM }; -#ifndef ZL_WEBSOCKETCONNECTION_PLATFORM -#define ZL_WEBSOCKETCONNECTION_PLATFORM + ZL_HttpConnection_Impl(); void Connect(const char* url); ZL_HTTPCONNECTION_PLATFORM }; +#ifndef ZL_WEBSOCKETCLIENT_PLATFORM +#define ZL_WEBSOCKETCLIENT_PLATFORM #endif -#define ZL_WEBSOCKETCONNECTION_IMPL_INTERFACE struct ZL_WebSocketConnection_Impl : ZL_Impl { ZL_String url; bool websocket_active; \ +#define ZL_WEBSOCKETCLIENT_IMPL_INTERFACE struct ZL_WebSocketClient_Impl : ZL_Impl { \ + bool websocket_active, started; \ + ZL_Signal_v0 sigConnected; ZL_Signal_v1 sigDisconnected; \ ZL_Signal_v1 sigReceivedText; \ - ZL_Signal_v2 sigReceivedBinary; \ - ZL_Signal_v0 sigConnected; ZL_Signal_v0 sigDisconnected; \ - ZL_WebSocketConnection_Impl(); void Connect(); void SendText(const char* buf, size_t len); void SendBinary(const void* buf, size_t len); void Disconnect(unsigned short code, const char* buf, size_t len); ZL_WEBSOCKETCONNECTION_PLATFORM }; + ZL_Signal_v2 sigReceivedBinary; \ + ZL_WebSocketClient_Impl(); void Connect(const char* url); void Send(const void* buf, size_t len, bool is_text); void Disconnect(unsigned short code, const char* buf, size_t len); ZL_WEBSOCKETCLIENT_PLATFORM }; //forward declaration of stuff that is used globally around the library code struct ZL_File_Impl; diff --git a/Source/ZL_PlatformEmscripten.cpp b/Source/ZL_PlatformEmscripten.cpp index 2672e90..36a4074 100644 --- a/Source/ZL_PlatformEmscripten.cpp +++ b/Source/ZL_PlatformEmscripten.cpp @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -42,7 +42,7 @@ extern int ZLJS_GetHeight(); extern void ZLJS_SetFullscreen(int flag); extern void ZLJS_SetPointerLock(int flag); extern unsigned int ZLJS_GetTime(); -extern void ZLJS_AsyncLoad(const char* url, struct ZL_HttpConnection_Impl*, char* postdata, size_t postlength); +extern void ZLJS_AsyncLoad(const char* url, struct ZL_HttpConnection_Impl*, char* postdata, size_t postlength, unsigned int timeout); extern void ZLJS_StartAudio(); extern void ZLJS_OpenExternalUrl(const char* url); extern void ZLJS_SettingsInit(const char* prefix); @@ -50,7 +50,7 @@ extern void ZLJS_SettingsSet(const char* key, const char* val); extern void ZLJS_SettingsDel(const char* key); extern bool ZLJS_SettingsHas(const char* key); extern char* ZLJS_SettingsGetMalloc(const char* key); -extern void ZLJS_Websocket(struct ZL_WebSocketConnection_Impl* impl, int cmd, const void* param, size_t len = 0); +extern void ZLJS_Websocket(struct ZL_WebSocketClient_Impl* impl, int cmd, const void* param, size_t len = 0); }; static void ZL_WindowEvent(unsigned char event, int data1 = 0, int data2 = 0); @@ -443,32 +443,32 @@ ZL_HTTPCONNECTION_IMPL_INTERFACE ZL_HttpConnection_Impl::ZL_HttpConnection_Impl() : timeout_msec(10000), dostream(false) { } extern "C" void ZLFNHTTP(ZL_HttpConnection_Impl* impl, int status, char* data, size_t length) { - if (impl->sigReceivedString.HasConnections()) impl->sigReceivedString.call(200, (length ? ZL_String(data, length) : ZL_String::EmptyString)); - if (impl->sigReceivedData.HasConnections()) impl->sigReceivedData.call(200, data, length); + if (impl->GetRefCount() == 1) { impl->DelRef(); return; } + if (impl->sigReceivedString.HasConnections()) impl->sigReceivedString.call(status, (length ? ZL_String(data, length) : ZL_String::EmptyString)); + if (impl->sigReceivedData.HasConnections()) impl->sigReceivedData.call(status, data, length); if (impl->dostream && length) { impl->dostream = 0; ZLFNHTTP(impl, status, NULL, 0); } //send a 0 byte length stream termination packet else impl->DelRef(); } -void ZL_HttpConnection_Impl::Connect() +void ZL_HttpConnection_Impl::Connect(const char* url) { - if (!url.length()) return; //ZL_LOG2("HTTP", "Loading URL: %s (Post data: %d bytes)", url.c_str(), post_data.size()); if (dostream) { ZL_LOG0("HTTP", "WARNING: Unsupported option dostream is activated for this http request. It will be received as one big packet with an additional terminating zero length packet."); } AddRef(); - ZLJS_AsyncLoad(url.c_str(), this, &post_data[0], post_data.size()); + ZLJS_AsyncLoad(url, this, &post_data[0], post_data.size(), timeout_msec); } -ZL_WEBSOCKETCONNECTION_IMPL_INTERFACE -ZL_WebSocketConnection_Impl::ZL_WebSocketConnection_Impl() : websocket_active(false) { } -extern "C" void ZLFNWebSocket(ZL_WebSocketConnection_Impl* impl, int status, char* data, size_t length) +/*ZL_WEBSOCKETCLIENT_IMPL_INTERFACE +ZL_WebSocketClient_Impl::ZL_WebSocketClient_Impl() : websocket_active(false), started(false) { } +extern "C" void ZLFNWebSocket(ZL_WebSocketClient_Impl* impl, int status, char* data, size_t length) { - if (!impl->websocket_active && status) { impl->websocket_active = true; impl->sigConnected.call(); } - if (status == 0) { if (impl->websocket_active) { impl->websocket_active = false; impl->sigDisconnected.call(); } } - else if (status == 2) { impl->sigReceivedText.call(ZL_String(data, length)); } - else if (status == 3) { impl->sigReceivedBinary.call(data, length); } + if (!impl->started || impl->GetRefCount() == 1) return; + if (!impl->websocket_active && status < 3) { impl->websocket_active = true; impl->sigConnected.call(); } + if (status == 1) { impl->sigReceivedText.call(ZL_String(data, length)); } + else if (status == 2) { impl->sigReceivedBinary.call(data, length); } + if (status >= 3 && impl->started) { impl->websocket_active = false; impl->sigDisconnected.call((unsigned short)(status - 3)); } } -void ZL_WebSocketConnection_Impl::Connect() { ZLJS_Websocket(this, 0, url.c_str()); } -void ZL_WebSocketConnection_Impl::SendText(const char* buf, size_t len) { ZLJS_Websocket(this, 1, buf, len); } -void ZL_WebSocketConnection_Impl::SendBinary(const void* buf, size_t len) { ZLJS_Websocket(this, 2, buf, len); } -void ZL_WebSocketConnection_Impl::Disconnect(unsigned short code, const char* buf, size_t len) { ZLFNWebSocket(this, 0, NULL, 0); ZLJS_Websocket(this, 3+code, buf, len); } +void ZL_WebSocketClient_Impl::Connect(const char* url) { Disconnect(1001, 0, 0); AddRef(); started = true; ZLJS_Websocket(this, 0, url); } +void ZL_WebSocketClient_Impl::Send(const void* buf, size_t len, bool is_text) { if (started) ZLJS_Websocket(this, (is_text ? 1 : 2), buf, len); } +void ZL_WebSocketClient_Impl::Disconnect(unsigned short code, const char* buf, size_t len) { if (started) { websocket_active = started = false; ZLJS_Websocket(this, 3+code, buf, len); DelRef(); } }*/ #endif //defined(__wasm__) || defined(__EMSCRIPTEN__) diff --git a/Source/ZL_PlatformNACL.cpp b/Source/ZL_PlatformNACL.cpp index 747b719..97df72d 100644 --- a/Source/ZL_PlatformNACL.cpp +++ b/Source/ZL_PlatformNACL.cpp @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -761,7 +761,7 @@ void ZL_SettingsSynchronize() ZL_HTTPCONNECTION_IMPL_INTERFACE -ZL_HttpConnection_Impl::ZL_HttpConnection_Impl() : dostream(false), urlloader(0) { } +ZL_HttpConnection_Impl::ZL_HttpConnection_Impl() : timeout_msec(10000), dostream(false), urlloader(0) { } void ZL_HttpConnection_Disconnect(ZL_HttpConnection_Impl *impl) { if (!impl->urlloader) return; @@ -824,12 +824,11 @@ void ZL_HttpConnection_Open_Callback(void* vimpl, int32_t result) const PP_CompletionCallback cc = { &ZL_HttpConnection_Read_Callback, vimpl, 0 }; ppb_urlloader_interface->ReadResponseBody(impl->urlloader, &impl->data[0], URLLOAD_BUFSIZE, cc); } -void ZL_HttpConnection_Impl::Connect() +void ZL_HttpConnection_Impl::Connect(const char* url) { - if (!url.length()) return; urlloader = ppb_urlloader_interface->Create(instance_); PP_Resource urlrequestinfo = ppb_urlrequestinfo_interface->Create(instance_); - ppb_urlrequestinfo_interface->SetProperty(urlrequestinfo, PP_URLREQUESTPROPERTY_URL, ZLStrToVar(url)); + ppb_urlrequestinfo_interface->SetProperty(urlrequestinfo, PP_URLREQUESTPROPERTY_URL, ppb_var_interface->VarFromUtf8(url, strlen(url))); ppb_urlrequestinfo_interface->SetProperty(urlrequestinfo, PP_URLREQUESTPROPERTY_ALLOWCROSSORIGINREQUESTS, PP_MakeBool(PP_TRUE)); ppb_urlrequestinfo_interface->SetProperty(urlrequestinfo, PP_URLREQUESTPROPERTY_RECORDDOWNLOADPROGRESS, PP_MakeBool(PP_TRUE)); if (post_data.size()) @@ -843,30 +842,31 @@ void ZL_HttpConnection_Impl::Connect() if (ppb_urlloader_interface->Open(urlloader, urlrequestinfo, cc) != PP_OK_COMPLETIONPENDING) ZL_HttpConnection_Open_Callback(cc.user_data, -1); } -ZL_WEBSOCKETCONNECTION_IMPL_INTERFACE -ZL_WebSocketConnection_Impl::ZL_WebSocketConnection_Impl() : websocket_active(false) { } -void ZL_WebSocketConnection_Impl::Connect() +ZL_WEBSOCKETCLIENT_IMPL_INTERFACE +ZL_WebSocketClient_Impl::ZL_WebSocketClient_Impl() : websocket_active(false) { } +void ZL_WebSocketClient_Impl::Connect(const char* url) { struct ZL_WebSocket_Callbacks { - static void OnConnect(ZL_WebSocketConnection_Impl* impl, int32_t result) + static void OnConnect(ZL_WebSocketClient_Impl* impl, int32_t result) { //ZL_LOG("NACLWSC", "ONCONNECT - WS: %d - RESULT: %d - READYSTATE: %d", impl->websocket, result, (int32_t)ppb_websocket_interface->GetReadyState(impl->websocket)); - if (result < 0) { impl->Disconnect(PP_WEBSOCKETSTATUSCODE_ABNORMAL_CLOSURE, NULL, 0); return; } + if (result < 0) { impl->sigDisconnected.call((uint16_t)ppb_websocket_interface->GetCloseCode(impl->websocket)); impl->websocket = 0; return; } impl->websocket_active = true; impl->sigConnected.call(); - if (ppb_websocket_interface->ReceiveMessage(impl->websocket, &impl->data, PP_MakeCompletionCallback((PP_CompletionCallback_Func)&OnReceiveMessage, impl)) == PP_OK) - OnReceiveMessage(impl, PP_OK); + impl->data = PP_MakeUndefined(); + OnReceiveMessage(impl, PP_OK_COMPLETIONPENDING); } - static void OnReceiveMessage(ZL_WebSocketConnection_Impl* impl, int32_t result) + static void OnReceiveMessage(ZL_WebSocketClient_Impl* impl, int32_t result) { //ZL_LOG("NACLWSC", "ONRECEIVE - WS: %d - RESULT: %d - READYSTATE: %d - BUFFER: %d", impl->websocket, result, (int32_t)ppb_websocket_interface->GetReadyState(impl->websocket), (int32_t)ppb_websocket_interface->GetBufferedAmount(impl->websocket)); - if (result < 0) { impl->Disconnect(PP_WEBSOCKETSTATUSCODE_ABNORMAL_CLOSURE, NULL, 0); return; } HandleMessage(impl); + int32_t lastPP; const PP_CompletionCallback cc = PP_MakeCompletionCallback((PP_CompletionCallback_Func)&OnReceiveMessage, (void*)impl); - while (ppb_websocket_interface->ReceiveMessage(impl->websocket, &impl->data, cc) == PP_OK) HandleMessage(impl); + while ((lastPP = ppb_websocket_interface->ReceiveMessage(impl->websocket, &impl->data, cc)) == PP_OK) HandleMessage(impl); + if (lastPP <= PP_ERROR_FAILED) { impl->sigDisconnected.call((uint16_t)ppb_websocket_interface->GetCloseCode(impl->websocket)); impl->websocket = 0; impl->websocket_active = false; } } - static void HandleMessage(ZL_WebSocketConnection_Impl* impl) + static void HandleMessage(ZL_WebSocketClient_Impl* impl) { //ZL_LOG("NACLWSC", "HANDLEMESSAGE - WS: %d - VALUE_ID: %d - READYSTATE: %d", impl->websocket, (int32_t)impl->data.value.as_id, (int32_t)ppb_websocket_interface->GetReadyState(impl->websocket)); if (!impl->data.value.as_id) return; @@ -880,35 +880,31 @@ void ZL_WebSocketConnection_Impl::Connect() { uint32_t length; ppb_vararraybuffer_interface->ByteLength(impl->data, &length); - const char* data = (const char*)ppb_vararraybuffer_interface->Map(impl->data); + const void* data = (const void*)ppb_vararraybuffer_interface->Map(impl->data); impl->sigReceivedBinary.call(data, length); + ppb_vararraybuffer_interface->Unmap(impl->data); } ppb_var_interface->Release(impl->data); } }; websocket = ppb_websocket_interface->Create(instance_); - ppb_websocket_interface->Connect(websocket, ZLStrToVar(url), NULL, 0, PP_MakeCompletionCallback((PP_CompletionCallback_Func)&ZL_WebSocket_Callbacks::OnConnect, this)); + ppb_websocket_interface->Connect(websocket, ppb_var_interface->VarFromUtf8(url, strlen(url)), NULL, 0, PP_MakeCompletionCallback((PP_CompletionCallback_Func)&ZL_WebSocket_Callbacks::OnConnect, this)); } -void ZL_WebSocketConnection_Impl::SendText(const char* buf, size_t len) +void ZL_WebSocketClient_Impl::Send(const void* buf, size_t len, bool is_text) { if (!websocket) return; - ppb_websocket_interface->SendMessage(websocket, ppb_var_interface->VarFromUtf8(buf, len)); -} -void ZL_WebSocketConnection_Impl::SendBinary(const void* buf, size_t len) -{ - if (!websocket) return; - PP_Var v = ppb_vararraybuffer_interface->Create(len); - memcpy(ppb_vararraybuffer_interface->Map(v), buf, len); + PP_Var v; + if (is_text) v = ppb_var_interface->VarFromUtf8((const char*)buf, len); + else { memcpy(ppb_vararraybuffer_interface->Map(v = ppb_vararraybuffer_interface->Create(len)), buf, len); ppb_vararraybuffer_interface->Unmap(v); } ppb_websocket_interface->SendMessage(websocket, v); } -void ZL_WebSocketConnection_Impl::Disconnect(unsigned short code, const char* buf, size_t len) +void ZL_WebSocketClient_Impl::Disconnect(unsigned short code, const char* buf, size_t len) { if (!websocket) return; struct ZL_WebSocket_Callbacks { static void OnDisconnect(void*, int32_t) {} }; ppb_websocket_interface->Close(websocket, code, (buf ? ppb_var_interface->VarFromUtf8(buf, len) : PP_MakeUndefined()), PP_MakeCompletionCallback(&ZL_WebSocket_Callbacks::OnDisconnect, NULL)); websocket = 0; websocket_active = false; - sigDisconnected.call(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Source/ZL_PlatformNACL.h b/Source/ZL_PlatformNACL.h index 24af1d8..37d0112 100644 --- a/Source/ZL_PlatformNACL.h +++ b/Source/ZL_PlatformNACL.h @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2016 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -38,7 +38,7 @@ void ZL_SetPointerLock(bool doLockPointer); //web network interfaces some platforms without sockets have to implement #define ZL_HTTPCONNECTION_PLATFORM PP_Resource urlloader; std::vector data; -#define ZL_WEBSOCKETCONNECTION_PLATFORM PP_Resource websocket; PP_Var data; +#define ZL_WEBSOCKETCLIENT_PLATFORM PP_Resource websocket; PP_Var data; //platform specific #define vsnprintf __builtin_vsnprintf diff --git a/WebAssembly/ZillaLibWasm.js b/WebAssembly/ZillaLibWasm.js index c33d018..548e0e0 100644 --- a/WebAssembly/ZillaLibWasm.js +++ b/WebAssembly/ZillaLibWasm.js @@ -1,6 +1,6 @@ /* ZillaLib - Copyright (C) 2010-2019 Bernhard Schelling + Copyright (C) 2010-2020 Bernhard Schelling This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages @@ -1137,24 +1137,26 @@ function ZLJS_WASM_IMPORTS(env) do_lock(f); }; - env.ZLJS_AsyncLoad = function(url, impl, postdata, postlength) + env.ZLJS_AsyncLoad = function(url, impl, postdata, postlength, timeout) { - var xhr = new XMLHttpRequest(); + var xhr = new XMLHttpRequest(), ZLFNHTTP = ZL.asm.ZLFNHTTP; xhr.open((postlength ? 'POST' : 'GET'), Pointer_stringify(url), true); xhr.responseType = 'arraybuffer'; + xhr.timeout = timeout; xhr.onload = function() { if (xhr.status == 200) { var b = malloc_array(new Uint8Array(xhr.response)); - ZL.asm.ZLFNHTTP(impl, 200, b, xhr.response.byteLength); + ZLFNHTTP(impl, 200, b, xhr.response.byteLength); ZL.asm.free(b); } - else ZL.asm.ZLFNHTTP(impl, xhr.status, 0, 0); + else ZLFNHTTP(impl, xhr.status, 0, 0); }; - xhr.onerror = function(event) + xhr.ontimeout = xhr.onerror = function(event) { - ZL.asm.ZLFNHTTP(impl, xhr.status, 0, 0); + // this could be called synchronously by xhr.send() so force it to arrive a frame later + setTimeout(function() { ZLFNHTTP(impl, xhr.status||-1, 0, 0); }); }; if (postlength) try { xhr.send(HEAPU8.subarray(postdata, postdata+postlength)); } catch (e) { xhr.send(HEAPU8.buffer.slice(postdata, postdata+postlength)); } else xhr.send(null); @@ -1162,19 +1164,20 @@ function ZLJS_WASM_IMPORTS(env) env.ZLJS_Websocket = function(impl, cmd, param, len) { + var w, ZLFNWebSocket = ZL.asm.ZLFNWebSocket; if (cmd == 1) { if (ws) ws.send(Pointer_stringify(param,len)); return; } if (cmd == 2) { if (ws) ws.send(HEAPU8.subarray(param, param+len)); return; } - if (cmd >= 3) { ws.close(cmd-3, len?Pointer_stringify(param,len):undefined); ws = undefined; return; } - var w = new WebSocket(Pointer_stringify(param)); + if (cmd >= 3) { if (ws) ws.close(cmd-3, len?Pointer_stringify(param,len):undefined); ws = undefined; return; } + try { var w = new WebSocket(Pointer_stringify(param)); } catch (e) { setTimeout(function() { ZLFNWebSocket(impl, 1009); }); return; } w.binaryType = 'arraybuffer'; - w.onopen = function() { ZL.asm.ZLFNWebSocket(impl, 1); }; + w.onopen = function() { ZLFNWebSocket(impl, 0); }; w.onmessage = function (evt) { var s = typeof evt.data === 'string', v = s ? evt.data : new Uint8Array(evt.data), b = s ? malloc_string(v) : malloc_array(v); - ZL.asm.ZLFNWebSocket(impl, s ? 2 : 3, b, v.length); + ZLFNWebSocket(impl, s ? 1 : 2, b, v.length); ZL.asm.free(b); }; - w.onclose = function() { ZL.asm.ZLFNWebSocket(impl, 0); ws = undefined; }; + w.onclose = function(evt) { ZLFNWebSocket(impl, 3+evt.code); ws = undefined; }; ws = w; }; diff --git a/WebAssembly/ZillaLibWasm.mk b/WebAssembly/ZillaLibWasm.mk index 065b97a..6065699 100644 --- a/WebAssembly/ZillaLibWasm.mk +++ b/WebAssembly/ZillaLibWasm.mk @@ -1,6 +1,6 @@ # # ZillaLib -# Copyright (C) 2010-2019 Bernhard Schelling +# Copyright (C) 2010-2020 Bernhard Schelling # # This software is provided 'as-is', without any express or implied # warranty. In no event will the authors be held liable for any damages @@ -51,7 +51,7 @@ else APPOUTDIR := Debug-wasm DBGCFLAGS := -DDEBUG -D_DEBUG -DZILLALOG LDFLAGS := - WOPTFLAGS := -O0 + WOPTFLAGS := -g -O0 endif # Project Build flags @@ -186,9 +186,9 @@ $(APPOUTDIR)/$(ZillaApp).bc : $(APPOBJS) $(APPOUTDIR)/$(ZillaApp).wasm : $(THIS_MAKEFILE) $(APPOUTDIR)/$(ZillaApp).bc $(LIBOUTDIR)/ZillaLib.bc $(SYSOUTDIR)/System.bc $(info Linking $@ ...) @$(LD) $(LDFLAGS) -o $@ $(APPOUTDIR)/$(ZillaApp).bc $(LIBOUTDIR)/ZillaLib.bc $(SYSOUTDIR)/System.bc -# D:\dev\emscripten_sdk\wabt\wasm-objdump.exe -x $@ >$@.objdump -# D:\dev\emscripten_sdk\wabt\wasm2c $@ -o $@.c @$(WASMOPT) --legalize-js-interface $(WOPTFLAGS) $@ -o $@ + D:\dev\emscripten_sdk\wabt\wasm-objdump.exe -x $@ >$@.objdump + D:\dev\emscripten_sdk\wabt\wasm2c $@ -o $@.c $(ASSET_ZIP) : $(if $(ASSET_ALL_STARS),assets.mk $(subst *,\ ,$(ASSET_ALL_STARS))) $(info Building $@ with $(words $(ASSET_ALL_STARS)) assets ...) @@ -290,6 +290,12 @@ LIBSOURCES := $(wildcard $(ZILLALIB_DIR)Source/*.cpp) DEPSOURCES := \ $(wildcard $(ZILLALIB_DIR)Source/zlib/*.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/callbacks.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/host.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/list.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/packet.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/peer.c) \ + $(wildcard $(ZILLALIB_DIR)Source/enet/protocol.c) \ $(wildcard $(ZILLALIB_DIR)Source/libtess2/*.c) \ $(wildcard $(ZILLALIB_DIR)Source/stb/*.cpp)