webquake-compatible websocket support. yuck.

git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@4563 fc73d0e0-1445-4013-8a0c-d673dee63da5
This commit is contained in:
Spoike 2013-12-09 01:18:27 +00:00
parent 483403dc9a
commit e2081b565f
4 changed files with 88 additions and 14 deletions

View File

@ -553,6 +553,16 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate)
send.maxsize = MAX_NQMSGLEN + PACKET_HEADER;
send.cursize = 0;
if (chan->remote_address.type == NA_TCP && chan->reliable_length)
{
//if over tcp, everything is assumed to be reliable. pretend it got acked.
chan->reliable_length = 0; //they got the entire message
chan->reliable_start = 0;
chan->incoming_reliable_acknowledged = chan->reliable_sequence;
chan->reliable_sequence++;
chan->nqreliable_allowed = true;
}
/*unreliables flood out, but reliables are tied to server sequences*/
if (chan->nqreliable_resendtime < realtime)
chan->nqreliable_allowed = true;
@ -572,7 +582,7 @@ int Netchan_Transmit (netchan_t *chan, int length, qbyte *data, int rate)
{
MSG_WriteLong(&send, 0);
MSG_WriteLong(&send, LongSwap(chan->reliable_sequence));
if (i > MAX_NQDATAGRAM)
if (i > MAX_NQDATAGRAM && chan->remote_address.type != NA_TCP)
i = MAX_NQDATAGRAM;
SZ_Write (&send, chan->reliable_buf+chan->reliable_start, i);

View File

@ -1572,6 +1572,14 @@ qboolean FTENET_Loop_GetPacket(ftenet_generic_connection_t *con)
return NET_GetLoopPacket(con->thesocket, &net_from, &net_message);
}
#ifdef HAVE_PACKET
//just a null function so we don't pass bad things to select.
int FTENET_Loop_SetReceiveFDSet(ftenet_generic_connection_t *gcon, fd_set *fdset)
{
return 0;
}
#endif
qboolean FTENET_Loop_SendPacket(ftenet_generic_connection_t *con, int length, void *data, netadr_t *to)
{
if (to->type == NA_LOOPBACK)
@ -1609,6 +1617,9 @@ static ftenet_generic_connection_t *FTENET_Loop_EstablishConnection(qboolean iss
newcon->GetPacket = FTENET_Loop_GetPacket;
newcon->SendPacket = FTENET_Loop_SendPacket;
newcon->Close = FTENET_Loop_Close;
#ifdef HAVE_PACKET
newcon->SetReceiveFDSet = FTENET_Loop_SetReceiveFDSet;
#endif
newcon->islisten = isserver;
newcon->addrtype[0] = NA_LOOPBACK;
@ -2605,6 +2616,7 @@ typedef struct ftenet_tcpconnect_stream_s {
TCPC_QIZMO, //'qizmo\n' handshake, followed by packets prefixed with a 16bit packet length.
TCPC_WEBSOCKETU, //utf-8 encoded data.
TCPC_WEBSOCKETB, //binary encoded data (subprotocol = 'binary')
TCPC_WEBSOCKETNQ, //raw nq msg buffers with no encapsulation or handshake
} clienttype;
char inbuffer[3000];
char outbuffer[3000];
@ -2612,6 +2624,8 @@ typedef struct ftenet_tcpconnect_stream_s {
float timeouttime;
netadr_t remoteaddr;
struct ftenet_tcpconnect_stream_s *next;
int fakesequence; //TCPC_WEBSOCKETNQ
} ftenet_tcpconnect_stream_t;
typedef struct {
@ -2896,29 +2910,56 @@ closesvstream:
char acceptkey[20*2];
unsigned char sha1digest[20];
char *blurgh;
char *protoname = "";
memmove(st->inbuffer, st->inbuffer+i, st->inlen - (i));
st->inlen -= i;
blurgh = va("%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", arg[WCATTR_WSKEY]);
tobase64(acceptkey, sizeof(acceptkey), sha1digest, SHA1(sha1digest, sizeof(sha1digest), blurgh, strlen(blurgh)));
Con_Printf("Websocket request for %s from %s\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr));
Con_Printf("Websocket request for %s from %s (%s)\n", arg[WCATTR_URL], NET_AdrToString (adr, sizeof(adr), &st->remoteaddr), arg[WCATTR_WSPROTO]);
resp = va( "HTTP/1.1 101 WebSocket Protocol Handshak\r\n"
if (!strcmp(arg[WCATTR_WSPROTO], "quake"))
{
st->clienttype = TCPC_WEBSOCKETNQ; //emscripten doesn't give us a choice, but its compact.
protoname = "Sec-WebSocket-Protocol: quake\r\n";
}
else if (!strcmp(arg[WCATTR_WSPROTO], "binary"))
{
st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact.
protoname = "Sec-WebSocket-Protocol: binary\r\n"; //emscripten is a bit limited
}
else
{
st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it.
protoname = va("Sec-WebSocket-Protocol: %s\r\n", arg[WCATTR_WSPROTO]); //emscripten is a bit limited
}
resp = va( "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
"Upgrade: websocket\r\n"
"Connection: Upgrade\r\n"
"Access-Control-Allow-Origin: *\r\n" //allow cross-origin requests. this means you can use any domain to play on any public server.
"Sec-WebSocket-Accept: %s\r\n"
// "Sec-WebSocket-Protocol: FTEWebSocket\r\n"
"\r\n", acceptkey);
// "%s"
"\r\n", acceptkey, protoname);
//send the websocket handshake response.
send(st->socketnum, resp, strlen(resp), 0);
//and the connection is okay
if (!strcmp(arg[WCATTR_WSPROTO], "binary"))
st->clienttype = TCPC_WEBSOCKETB; //emscripten doesn't give us a choice, but its compact.
else
st->clienttype = TCPC_WEBSOCKETU; //nacl supports only utf-8 encoded data, at least at the time I implemented it.
if (st->clienttype == TCPC_WEBSOCKETNQ)
{
//hide a connection request in there...
net_message.cursize = 0;
net_message.packing = SZ_RAWBYTES;
net_message.currentbit = 0;
net_from = st->remoteaddr;
MSG_WriteLong(&net_message, LongSwap(NETFLAG_CTL | (strlen(NET_GAMENAME_NQ)+7)));
MSG_WriteByte(&net_message, CCREQ_CONNECT);
MSG_WriteString(&net_message, NET_GAMENAME_NQ);
MSG_WriteByte(&net_message, NET_PROTOCOL_VERSION);
return true;
}
}
}
else
@ -3053,6 +3094,7 @@ handshakeerror:
return true;
case TCPC_WEBSOCKETU:
case TCPC_WEBSOCKETB:
case TCPC_WEBSOCKETNQ:
while (st->inlen >= 2)
{
unsigned short ctrl = ((unsigned char*)st->inbuffer)[0]<<8 | ((unsigned char*)st->inbuffer)[1];
@ -3175,12 +3217,22 @@ handshakeerror:
case 2: /*binary frame*/
// Con_Printf ("websocket binary frame from %s\n", NET_AdrToString (adr, sizeof(adr), st->remoteaddr));
net_message.cursize = paylen;
if (net_message.cursize >= sizeof(net_message_buffer) )
if (net_message.cursize+8 >= sizeof(net_message_buffer) )
{
Con_TPrintf ("Warning: Oversize packet from %s\n", NET_AdrToString (adr, sizeof(adr), &net_from));
goto closesvstream;
}
memcpy(net_message_buffer, st->inbuffer+payoffs, paylen);
if (st->clienttype == TCPC_WEBSOCKETNQ)
{
payoffs+=1;
paylen-=1;
memcpy(net_message_buffer+8, st->inbuffer+payoffs, paylen);
net_message.cursize=paylen+8;
((int*)net_message_buffer)[0] = BigLong(NETFLAG_UNRELIABLE | net_message.cursize);
((int*)net_message_buffer)[1] = LongSwap(++st->fakesequence);
}
else
memcpy(net_message_buffer, st->inbuffer+payoffs, paylen);
break;
case 8: /*connection close*/
Con_Printf ("websocket closure %s\n", NET_AdrToString (adr, sizeof(adr), &st->remoteaddr));
@ -3284,11 +3336,20 @@ qboolean FTENET_TCPConnect_SendPacket(ftenet_generic_connection_t *gcon, int len
memcpy(st->outbuffer, data, length);
st->outlen = length;
break;
case TCPC_WEBSOCKETNQ:
if (length < 8 || ((char*)data)[0] & 0x80)
break;
// length = 2;
// data = "\1\1";
length-=7;
data=(char*)data + 7;
*(char*)data = 1; //for compat with webquake, we add an extra byte at the start. 1 for reliable, 2 for unreliable.
//fallthrough
case TCPC_WEBSOCKETU:
case TCPC_WEBSOCKETB:
{
/*as a server, we don't need the mask stuff*/
unsigned short ctrl = (st->clienttype==TCPC_WEBSOCKETB)?0x8200:0x8100;
unsigned short ctrl = (st->clienttype==TCPC_WEBSOCKETU)?0x8100:0x8200;
unsigned int paylen = 0;
unsigned int payoffs = 2;
int i;

View File

@ -125,7 +125,7 @@ cvar_t allow_download_copyrighted = CVAR("allow_download_copyrighted", "0");
cvar_t sv_public = CVAR("sv_public", "0");
cvar_t sv_listen_qw = CVARAF("sv_listen_qw", "1", "sv_listen", 0);
cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server. 0 = don't let them in. 1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks). 2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol).");
cvar_t sv_listen_nq = CVARD("sv_listen_nq", "2", "Allow new (net)quake clients to connect to the server.\n0 = don't let them in.\n1 = allow them in (WARNING: this allows 'qsmurf' DOS attacks).\n2 = accept (net)quake clients by emulating a challenge (as secure as QW/Q2 but does not fully conform to the NQ protocol).");
cvar_t sv_listen_dp = CVAR("sv_listen_dp", "0"); /*kinda fucked right now*/
cvar_t sv_listen_q3 = CVAR("sv_listen_q3", "0");
cvar_t sv_reportheartbeats = CVAR("sv_reportheartbeats", "1");
@ -2625,6 +2625,7 @@ client_t *SVC_DirectConnect(void)
{
SV_AcceptMessage (protocol);
newcl->state = cs_free;
if (ISNQCLIENT(newcl))
{
//FIXME: we should delay this until we actually have a name, because right now they'll be called unnamed or unconnected or something
@ -2643,6 +2644,7 @@ client_t *SVC_DirectConnect(void)
SV_BroadcastTPrintf(PRINT_LOW, "client %s connected\n", newcl->name);
// Con_DPrintf ("Client %s connected\n", newcl->name);
}
newcl->state = cs_connected;
}
else
{

View File

@ -425,6 +425,7 @@ void SVNQ_New_f (void)
int op;
unsigned int protext1 = 0, protext2 = 0, protmain = 0, protfl = 0;
char *protoname;
extern cvar_t sv_listen_nq;
host_client->prespawn_stage = PRESPAWN_INVALID;
host_client->prespawn_idx = 0;
@ -438,7 +439,7 @@ void SVNQ_New_f (void)
return;
}
if (!host_client->pextknown)
if (!host_client->pextknown && sv_listen_nq.ival != 1) //1 acts as a legacy mode, used for clients that can't cope with cmd before serverdata (either because they crash out or because they refuse to send reliables until after they got the first serverdata)
{
char *msg = va("cmd pext\n");
ClientReliableWrite_Begin (host_client, svc_stufftext, 2+strlen(msg));