| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- /******************************************************************************/
- #include "stdafx.h"
- namespace EE{
- /******************************************************************************/
- #define CONNECTION_CC4 CC4('E','E','N','C') // Esenthel Engine Network Connection
- #define CONNECTION_VERSION 0
- #define MSG_SIZE_KNOWN 0xFF
- #define SEND_WAIT_TIME (15*1000)
- #define LOG_ERROR 0
- #if LOG_ERROR
- #pragma message("!! Warning: Use this only for debugging !!")
- #endif
- /******************************************************************************/
- // CONNECTION
- /******************************************************************************/
- Connection& Connection::del()
- {
- if(_state==CONNECT_GREETED)flush(0); // if we were connected then flush any data, don't attempt to flush in other states (like sending a greeting for example)
- _state =CONNECT_INVALID;
- _birth =0;
- _msg_size_progress=0;
- _msg_size =0;
- _socket.del();
- _out .del();
- data .del();
- // don't clear '_in_offset' '_out_offset' '_address' as they may be called after connection got deleted (deletion may occur upon error/connection lost and we want to keep the stats even after that)
- return T;
- }
- Bool Connection::serverAcceptClient(Socket &server)
- {
- del();
- _in_offset=_out_offset=0; // clear at this stage because it's needed before 'greet', also make sure to always clear at connection time for stats usage
- if(server.accept(_socket, _address))
- {
- _socket.block(false);
- _birth=Time.curTimeMs();
- return greet();
- }
- return false;
- }
- Connection& Connection::clientConnectToServer(C SockAddr &server_addr)
- {
- del();
- _in_offset=_out_offset=0; // clear at this stage because it's needed before 'greet', also make sure to always clear at connection time for stats usage
- if(_socket.createTcp(server_addr))
- {
- _socket.block(false);
- _address=server_addr;
- _birth =Time.curTimeMs();
- switch(_socket.connect(server_addr, 0))
- {
- case Socket::FAILED : del(); break;
- case Socket::CONNECTED : greet(); break; // 'greet' sets the '_state'
- case Socket::IN_PROGRESS: _state=CONNECT_CONNECTING; break;
- }
- }
- return T;
- }
- /******************************************************************************/
- UInt Connection::life()C
- {
- return (_state!=CONNECT_INVALID) ? Time.curTimeMs()-_birth : 0; // this code was tested OK for UInt overflow
- }
- /******************************************************************************/
- Bool Connection::greet()
- {
- _out .writeMem(4*1024);
- data.writeMem(4*1024);
- _cipher.randomizeKey();
- // since we need to know the size of the cipher key up-front, first let's save it to 'data'
- if(!_cipher.saveKey(data))goto error;
- _out.cmpUIntV(4+1+data.size() ); // message length
- _out.putUInt (CONNECTION_CC4 ); // 4
- _out.putByte (CONNECTION_VERSION); // 1
- data.pos(0); data.copy(_out); data.reset(); // cipher key
- _out.pos(0);
- _state=CONNECT_AWAIT_GREET;
- if(flush())return true;
- error:
- del(); return false;
- }
- /******************************************************************************/
- Bool Connection::flush(Int timeout)
- {
- for(;;)
- {
- if(_out.end()){_out.reset(); return true;} // all data was sent
- if(!_socket.flush((timeout<0) ? SEND_WAIT_TIME : timeout))return false; // socket is busy
- if(timeout>0)timeout=0; // don't wait for next steps
- Byte buf[(64+1)*1024]; // 1 extra for the headers
- Int left=Min(SIZEI(buf), _out.left()); if(!_out.get(buf, left))break; if(_state==CONNECT_GREETED)_cipher.encrypt(buf, buf, left, _out_offset); // read data
- Int sent=_socket.send(buf, left); // send data
- if(sent<0 || sent>left)sent=0; // if an error occurred then clear sent. "sent==0" is also an error, but in this statement we don't need to change sent=0 -> 0
- if(_state==CONNECT_GREETED)_out_offset+=sent;
- if(left!=sent) // if haven't sent everything
- {
- _out.skip(sent-left); // go back the amount of bytes that weren't sent
- if(!sent) // if nothing was sent
- {
- if(Socket::WouldBlock()) // this can happen if data doesn't fit in the buffer
- {
- if(timeout<0)continue; // if we have unlimited timeout, then try again
- return false; // flush failed, but don't delete
- }
- break; // break so we can delete
- }
- }
- }
- _state=CONNECT_INVALID; // this will prevent calling 'flush' in the 'del' method
- del(); return false;
- }
- Bool Connection::flushEx(Int timeout)
- {
- return flush() && _socket.flush((timeout<0) ? SEND_WAIT_TIME : timeout);
- }
- /******************************************************************************/
- Bool Connection::updateEx(Int timeout, Bool read)
- {
- UInt end_time; // this function was tested OK for UInt overflow
- Bool adjust_time; // if adjust 'timeout' based on 'Time.curTimeMs' and 'end_time' before making some func call
- switch(_state)
- {
- case CONNECT_CONNECTING:
- {
- if(timeout>0)end_time=Time.curTimeMs()+timeout;
- if(_socket.any(timeout) && greet()){adjust_time=true; goto read_data;} // connected
- if(_socket.connectFailed())goto error;
- }break;
- case CONNECT_GREETED:
- {
- if(!read) // if we're not going to try and read a message (which can detect disconnects), then we have to at least check if we've got disconnected
- {
- if(_socket.wait(0) && _socket.available()<=0) // connection lost
- {
- #if LOG_ERROR
- LogN(S+"Connection.updateEx 0:"+PLATFORM(WSAGetLastError(), errno)+", available:"+_socket.available());
- #endif
- _state=CONNECT_INVALID; goto error; // set '_state' to prevent calling 'flush' in the 'del' method
- }
- return true;
- } // otherwise continue below
- case CONNECT_AWAIT_GREET:
- // in this section only 'end_time' and 'adjust_time' should be set
- if(timeout>0){end_time=Time.curTimeMs()+timeout; adjust_time=false;}
- // in this section only 'end_time' and 'adjust_time' should be set
- read_data:
- for(; _msg_size_progress!=MSG_SIZE_KNOWN; ) // unknown packet size
- {
- if(timeout>0)if(adjust_time)MAX(timeout=end_time-Time.curTimeMs(), 0);else adjust_time=true;
- if(!_socket.wait(timeout))return false; // if there's no data yet then abort
- Byte b; Int l=_socket.receive(&b, 1); // '_msg_size' is compressed using 'File.cmpUIntV', so we need to decompress it byte by byte until last read byte doesn't have the last bit enabled (b&128)
- if(l!=1) // connection lost
- {
- #if LOG_ERROR
- LogN(S+"Connection.updateEx 1:"+PLATFORM(WSAGetLastError(), errno)+", receive:"+l);
- #endif
- _state=CONNECT_INVALID; goto error; // set '_state' to prevent calling 'flush' in the 'del' method
- }
- if(_state==CONNECT_GREETED){_cipher.decrypt(&b, &b, 1, _in_offset); _in_offset++;} // decrypt
- _msg_size|=((b&127)<<(_msg_size_progress*7)); // each step grants knowledge of 7 bits
- if(b&128)_msg_size_progress++;else // need more info
- {
- data.reset(); // reset for upcoming data
- _msg_size_progress=((_msg_size<=0) ? 0 : MSG_SIZE_KNOWN); // if the message size was zero, then ignore it, and read the next one
- }
- }
- if(_msg_size_progress==MSG_SIZE_KNOWN)for(; _msg_size>0; ) // we await data
- {
- if(timeout>0)if(adjust_time)MAX(timeout=end_time-Time.curTimeMs(), 0);else adjust_time=true;
- if(!_socket.wait(timeout))return false; // if there's no data yet then abort
- Byte buf[65536];
- Int want=Min(SIZEU(buf), _msg_size),
- got =_socket.receive(buf, want);
- if( got<=0 || got>want) // connection lost
- {
- #if LOG_ERROR
- LogN(S+"Connection.updateEx 2:"+PLATFORM(WSAGetLastError(), errno)+", receive:"+got);
- #endif
- _state=CONNECT_INVALID; goto error; // set '_state' to prevent calling 'flush' in the 'del' method
- }
- if(_state==CONNECT_GREETED){_cipher.decrypt(buf, buf, got, _in_offset); _in_offset+=got;} // decrypt
- data.put(buf, got);
- _msg_size-=got;
- if(_msg_size<=0) // we've received entire message
- {
- _msg_size_progress=0;
- data.pos(0);
- if(_state==CONNECT_AWAIT_GREET)
- {
- if(data.getUInt()!=CONNECTION_CC4 )goto error;
- if(data.getByte()!=CONNECTION_VERSION){del()._state=CONNECT_VERSION_CONFLICT; return false;}
- if(!_cipher.mixKey(data) )goto error;
- if(!data.end() )goto error; // there should be no data left after the cipher key
- _state=CONNECT_GREETED;
- data.reset(); // don't leave the greeting for the user
- if(read)goto read_data; // here we've only greeted, however if we want an actual message, then try again
- // proceed below to return 'true' as success
- }
- return true; // return this message to the user
- }
- }
- }break;
- }
- return false;
- error:
- del(); return false;
- }
- Bool Connection::updateState(Int timeout) {return updateEx(timeout, false);}
- Bool Connection::receive (Int timeout) {return updateEx(timeout, true );}
- /******************************************************************************/
- Bool Connection::send(CPtr buf, Int size, Bool flush)
- {
- if(size>0 && _state==CONNECT_GREETED)
- {
- Int pos=_out.pos(); _out.pos(_out.size());
- _out.cmpUIntV(size);
- _out.put (buf, size); // size
- _out.pos (pos);
- return flush ? T.flush() : true;
- }
- return size==0;
- }
- Bool Connection::send(File &f, Int size, Bool flush)
- {
- if(size<0)size=f.left();
- if(size>0 && _state==CONNECT_GREETED)
- {
- Int pos=_out.pos(); _out.pos(_out.size());
- _out.cmpUIntV(size);
- for(; size>0; )
- {
- Byte buf[65536];
- Int l=Min(SIZEU(buf), size);
- f .get(buf, l);
- _out.put(buf, l); // size
- size-=l;
- }
- _out.pos(pos);
- return flush ? T.flush() : true;
- }
- return size==0;
- }
- /******************************************************************************/
- // FAST CONNECTION
- /******************************************************************************/
- void FastConnection::del()
- {
- _socket.del();
- // don't clear '_sent' '_received' so they can be read after connection is deleted
- }
- Bool FastConnection::create(Int port)
- {
- del(); _sent=_received=0; // instead of clearing params in 'del', clear them here
- SockAddr addr; addr.setServer(port);
- if(_socket.createUdp(addr))
- {
- _socket.block(false);
- if(port>=0) // try specified port
- {
- if(_socket.bind(addr))return true;
- }else
- REP(0x10000)if(_socket.bind(addr.port(i)))return true; // find first available
- }
- del(); return false;
- }
- Bool FastConnection::create(C SockAddr &addr)
- {
- del(); _sent=_received=0; // instead of clearing params in 'del', clear them here
- if(_socket.createUdp(addr) && _socket.bind(addr))
- {
- _socket.block(false);
- return true;
- }
- del(); return false;
- }
- /******************************************************************************/
- Bool FastConnection::send(C SockAddr &addr, CPtr data, Int size, Cipher *cipher)
- {
- if(size>=0 && size<=65536)
- {
- Byte temp[65536]; if(cipher){cipher->encrypt(temp, data, size, 0); data=temp;}
- Int sent=_socket.send(addr, data, size);
- if( sent>0)T._sent+=sent;
- return sent==size;
- }
- return false;
- }
- Bool FastConnection::send(C SockAddr &addr, File &f, Cipher *cipher)
- {
- Int left =f.left();
- if( left<=65536)
- {
- Byte temp[65536]; f.get(temp, left);
- return send(addr, temp, left, cipher);
- }
- return false;
- }
- Int FastConnection::receive(SockAddr &addr, Byte (&data)[65536])
- {
- Int received=_socket.receive(addr, data, SIZE(data));
- if( received>0)T._received+=received;
- return received;
- }
- /******************************************************************************/
- // CONNECTION SERVER
- /******************************************************************************/
- Bool ConnectionServer::Client::update()
- {
- if(connection.updateState(0))return true;
- switch(connection.state())
- {
- case CONNECT_CONNECTING :
- case CONNECT_AWAIT_GREET: return connection.life()<=5000; // allow some time to connect
- }
- return false;
- }
- /******************************************************************************/
- ConnectionServer::ConnectionServer() : clients(Compare)
- {
- }
- /******************************************************************************/
- void ConnectionServer::del()
- {
- _server .del();
- clients.del();
- }
- Bool ConnectionServer::create(Int port)
- {
- del();
- SockAddr addr; addr.setServer(port);
- if(_server.createTcp(addr))
- {
- _server.block(false); // disable blocking before calling 'listen' in case that would block
- if(port>=0) // try specified port
- {
- if(_server.bind(addr))if(_server.listen())return true;
- }else
- REP(0x10000)if(_server.bind(addr.port(i))) // find first available
- {
- if(_server.listen())return true;
- break;
- }
- }
- del(); return false;
- }
- Bool ConnectionServer::create(C SockAddr &addr)
- {
- del();
- if(_server.createTcp(addr))
- {
- _server.block(false); // disable blocking before calling 'listen' in case that would block
- if(_server.bind(addr))if(_server.listen())return true;
- }
- del(); return false;
- }
- Str ConnectionServer::localAddressName()C
- {
- if(is())return GetComputerName()+':'+port();
- return S;
- }
- SockAddr ConnectionServer::localAddress()C
- {
- SockAddr addr; if(is())addr.setLocal(port()); // can't use "_server.addr()" because if it was created with 'SockAddr.setServer' (INADDR_ANY) then that address will be returned
- return addr;
- }
- SockAddr ConnectionServer::globalAddress()C
- {
- SockAddr addr; if(is())addr.setGlobal(port());
- return addr;
- }
- /******************************************************************************/
- void ConnectionServer::update()
- {
- // accept new clients
- for(; _server.wait(0); )
- {
- Connection connection; if(connection.serverAcceptClient(_server))
- {
- Client &client=*clients(connection.address());
- Swap(client.connection, connection);
- client.create(T);
- }
- }
- // update clients
- REPA(clients)if(!clients[i].update())clients.remove(i);
- }
- /******************************************************************************/
- }
- /******************************************************************************/
|