1 /*
2  * file com_dgram.c - base struct und functions for datagram connections
3  *
4  * $Id: com_dgram.c,v 1.20 2006/02/24 21:29:16 fzago Exp $
5  *
6  * Program XBLAST
7  * (C) by Oliver Vogel (e-mail: m.vogel@ndh.net)
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published
11  * by the Free Software Foundation; either version 2; or (at your option)
12  * any later version
13  *
14  * This program is distributed in the hope that it will be entertaining,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILTY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
17  * Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License along
20  * with this program; if not, write to the Free Software Foundation, Inc.
21  * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "xblast.h"
25 
26 /*
27  * local macros
28  */
29 #define GAME_TIME_PING     0xFFFF
30 #define GAME_TIME_RESERVED 0xFFFE
31 
32 /* finish player mask as char array */
33 static unsigned char PLAYER_MASK_FINISH[MAX_MASK_BYTES] = { 0xFF, 0xFF, 0xFF, 0xFF };
34 
35 /*
36  * local variables
37  */
38 static unsigned char buffer[MAX_DGRAM_SIZE];
39 
40 /*
41  * pack player action with given number of mask bytes
42  */
43 static size_t
PackPlayerAction(PackedPlayerAction * dst,const PlayerAction * src,size_t mbytes)44 PackPlayerAction (PackedPlayerAction * dst, const PlayerAction * src, size_t mbytes)
45 {
46 	size_t i, j;
47 	unsigned char action;
48 	unsigned int intmask;
49 	assert (NULL != dst);
50 	assert (NULL != src);
51 
52 	/* create packed action data and integer mask */
53 	intmask = 0;
54 	for (i = 0, j = 0; i < MAX_PLAYER; i++) {
55 		action = PlayerActionToByte (src + i);
56 		if (0 != action) {
57 			intmask |= (1u << i);
58 			dst->action[j] = action;
59 			j++;
60 		}
61 	}
62 	/* number of actions byte to send */
63 	dst->numBytes = j;
64 	/* convert integer mask to char array, low to high */
65 	for (i = 0, j = 0; i < mbytes; i++, j += 8) {
66 		dst->mask[i] = 0xFF & (intmask >> j);
67 	}
68 	return dst->numBytes + mbytes;
69 }								/* PackPlayerAction */
70 
71 /*
72  * unpack player action, assume given number of mask bytes
73  */
74 static size_t
UnpackPlayerAction(PlayerAction * dst,const unsigned char * buf,size_t mbytes)75 UnpackPlayerAction (PlayerAction * dst, const unsigned char *buf, size_t mbytes)
76 {
77 	size_t i, j;
78 	unsigned int test, intmask;
79 
80 	assert (NULL != dst);
81 	assert (NULL != buf);
82 
83 	/* get integer mask from buffer */
84 	intmask = 0;
85 	for (i = 0, j = 0; j < mbytes; i += 8, j++) {
86 		intmask += (buf[j] << i);
87 	}
88 	/* extract player actions using integer mask */
89 	for (i = 0, j = mbytes, test = 1; i < MAX_PLAYER; i++, test <<= 1u) {
90 		if (intmask & test) {
91 			PlayerActionFromByte (dst + i, buf[j]);
92 			j++;
93 		}
94 		else {
95 			PlayerActionFromByte (dst + i, 0x00);
96 		}
97 	}
98 	/* return number of parsed bytes */
99 	return j;
100 }								/* UnpackPlayerAction */
101 
102 /*
103  * show current status
104  */
105 static void
CurrentStatus(XBCommDgram * dComm)106 CurrentStatus (XBCommDgram * dComm)
107 {
108 	assert (NULL != dComm);
109 	Dbg_Dgram ("rcv=(%lu,%lu), buf=(%lu,%lu), snd=(%lu,%lu), exp=%lu, queue=%lu\n",
110 			   dComm->rcvfirst, (unsigned long)dComm->rcvfirst ? (unsigned long)dComm->rcvnext - 1 : 0,
111 			   dComm->buffirst, (unsigned long)dComm->buffirst ? (unsigned long)dComm->bufnext - 1 : 0,
112 			   dComm->sndfirst, (unsigned long)dComm->sndfirst ? (unsigned long)dComm->sndnext - 1 : 0,
113 			   (unsigned long)dComm->expect, (unsigned long)dComm->queue);
114 }								/* CurrentStatus */
115 
116 /*
117  * handle a received ping time
118  */
119 static void
HandlePing(XBCommDgram * dComm,const unsigned char * data,size_t len)120 HandlePing (XBCommDgram * dComm, const unsigned char *data, size_t len)
121 {
122 	size_t i, j;
123 
124 	assert (NULL != dComm);
125 	/* call pingFunc once for each received time */
126 	if (len > 0) {
127 		assert (NULL != data);
128 		for (i = 1, j = 0; i < MAX_HOSTS && j < len; i++, j += 2) {
129 			(*dComm->pingFunc) (dComm, i, (data[j + 1] << 8) + data[j]);
130 		}
131 	}
132 	/* call pingFunc for empty ping */
133 	(*dComm->pingFunc) (dComm, 0, 0);
134 	Dbg_Dgram ("handle pings, len=%lu\n", (unsigned long)len);
135 }								/* HandlePing */
136 
137 /*
138  * handle received frames data
139  */
140 static void
HandleFrames(XBCommDgram * dComm,unsigned gameTime,const unsigned char * data,size_t len)141 HandleFrames (XBCommDgram * dComm, unsigned gameTime, const unsigned char *data, size_t len)
142 {
143 	size_t i;
144 	XBBool ignored;
145 	static PlayerAction playerAction[MAX_PLAYER];
146 
147 	/* this frame is in the future ... */
148 	assert (NULL != dComm->infoFunc);
149 	if (gameTime > dComm->expect) {
150 		Dbg_Out ("DGRAM: handle frames %ld-%d lost\n", (unsigned long)dComm->expect, gameTime - 1);
151 		if ((*dComm->infoFunc) (dComm, XBDI_LOSS)) {
152 			return;
153 		}
154 	}
155 	/* set first frame to send for client */
156 	/* TODO: why is that needed? if needed, move to com_dg_server.c!
157 	   if (! dComm->primary) {
158 	   dComm->queue = gameTime;
159 	   }
160 	 */
161 	dComm->rcvfirst = gameTime;
162 	i = 0;
163 	while (i + dComm->maskbytes - 1 < len) {
164 		ignored = XBFalse;
165 		/* extract finish or player action */
166 		if (memcmp (PLAYER_MASK_FINISH, data + i, dComm->maskbytes) == 0) {
167 			Dbg_Dgram ("FINISH received for gt=%u\n", gameTime);
168 			(void)((*dComm->infoFunc) (dComm, XBDI_FINISH));
169 			i += dComm->maskbytes;
170 		}
171 		else {
172 			i += UnpackPlayerAction (playerAction, data + i, dComm->maskbytes);
173 			if (gameTime != dComm->expect) {
174 				Dbg_Dgram ("ignoring action for gt=%u, expected gt=%lu\n", gameTime, (unsigned long)dComm->expect);
175 				dComm->ignore = gameTime;
176 				(void)((*dComm->infoFunc) (dComm, XBDI_IGNORE));
177 				ignored = XBTrue;
178 			}
179 			else {
180 				Dbg_Dgram ("accepting action for gt=%u\n", gameTime);
181 				assert (dComm->actionFunc != NULL);
182 				(*dComm->actionFunc) (dComm, gameTime, playerAction);
183 			}
184 		}
185 		/* adjust datagrams to send */
186 		if (!ignored) {
187 			dComm->expect++;
188 		}
189 		/* ready for next frame */
190 		gameTime++;
191 	}
192 	if (len > i) {
193 		Dbg_Dgram ("ignoring %lu of received bytes\n", (unsigned long)(len - i));
194 	}
195 	dComm->rcvnext = gameTime;
196 	(void)((*dComm->infoFunc) (dComm, XBDI_PARSED));
197 	/*
198 	   if (dComm->primary) {
199 	   dComm->queue = gameTime;
200 	   }
201 	 */
202 }								/* HandleFrames */
203 
204 /*
205  * receive datagram from readable socket
206  */
207 static XBCommResult
ReadDgram(XBComm * comm)208 ReadDgram (XBComm * comm)
209 {
210 	XBDatagram *rcv;
211 	const unsigned char *data;
212 	size_t len;
213 	unsigned gameTime;
214 	const char *host;
215 	unsigned short port;
216 	XBCommDgram *dComm = (XBCommDgram *) comm;
217 
218 	assert (NULL != dComm);
219 	if (NULL == dComm->host) {
220 		/* get datagram for connected socket */
221 		rcv = Net_ReceiveDatagram (comm->socket);
222 		if (rcv == NULL) {
223 			Dbg_Dgram ("failed to parse datagram, ignoring\n");
224 			return XCR_OK;
225 		}
226 		Dbg_Dgram ("rcv datagram on connected socket\n");
227 	}
228 	else {
229 		/* get datagram plus sender for unconnected socket */
230 		rcv = Net_ReceiveDatagramFrom (comm->socket, &host, &port);
231 		if (rcv == NULL) {
232 			Dbg_Dgram ("failed to parse datagram, ignoring\n");
233 			return XCR_OK;
234 		}
235 		Dbg_Dgram ("rcv datagram from %s:%u\n", host, port);
236 		/* match hosts */
237 		if (0 == strcmp (host, dComm->host)) {
238 			dComm->connected = Net_ConnectUdp (comm->socket, host, port);
239 			if (dComm->connected) {
240 				Dbg_Dgram ("successfully connected!\n");
241 				/* no further hostname checking */
242 				dComm->host = NULL;
243 				(void)(*dComm->infoFunc) (dComm, XBDI_CONSUCC);
244 			}
245 			else {
246 				Dbg_Dgram ("failed to connect!\n");
247 				assert (NULL != dComm->infoFunc);
248 				if ((*dComm->infoFunc) (dComm, XBDI_CONFAIL)) {
249 					Net_DeleteDatagram (rcv);
250 					return XCR_Error;
251 				}
252 			}
253 		}
254 		else {
255 			Dbg_Dgram ("datagram from unexpected host, ignoring!\n");
256 			Net_DeleteDatagram (rcv);
257 			return XCR_OK;
258 		}
259 	}
260 	assert (NULL != rcv);
261 	/* save reception time for ping calculation */
262 	gettimeofday (&dComm->lastRcv, NULL);
263 	/* copy data for application */
264 	data = Net_DgramData (rcv, &len);
265 	if (len == 0) {
266 		/* no data -> empty ping */
267 		HandlePing (dComm, data, 0);
268 	}
269 	else if (len >= 2) {
270 		/* first two bytes determine type otherwise */
271 		gameTime = (data[1] << 8) + data[0];
272 		if (GAME_TIME_PING == gameTime) {
273 			/* list of ping times */
274 			HandlePing (dComm, data + 2, len - 2);
275 		}
276 		else if (GAME_TIME_RESERVED == gameTime) {
277 			/* reserved type for future extensions */
278 		}
279 		else {
280 			/* frame data for given gametime */
281 			HandleFrames (dComm, gameTime, data + 2, len - 2);
282 		}
283 	}
284 	/* received datagram is parsed now */
285 	Net_DeleteDatagram (rcv);
286 	CurrentStatus (dComm);
287 	return XCR_OK;
288 }								/* ReadDgram */
289 
290 /*
291  * write current datagram to writeable socket
292  */
293 static XBCommResult
WriteDgram(XBComm * comm)294 WriteDgram (XBComm * comm)
295 {
296 	XBCommDgram *dComm = (XBCommDgram *) comm;
297 
298 	assert (NULL != comm);
299 	/* unregister for next socket loop */
300 	Socket_UnregisterWrite (CommSocket (comm));
301 	/* buffer should be non-NULL, but assert fails occasionally */
302 	/* assert(NULL != dComm->snd); */
303 	if (NULL != dComm->snd) {
304 		/* try to send */
305 		if (!Net_SendDatagram (dComm->snd, comm->socket)) {
306 			Dbg_Dgram ("failed to send datagram!\n");
307 			assert (NULL != dComm->infoFunc);
308 			return (*dComm->infoFunc) (dComm, XBDI_WRITEERR) ? XCR_Error : XCR_OK;
309 		}
310 		/* success, update send info for frames */
311 		dComm->sndfirst = dComm->buffirst;
312 		dComm->sndnext = dComm->bufnext;
313 		/* clear buffer */
314 		Net_DeleteDatagram (dComm->snd);
315 		dComm->snd = NULL;
316 		dComm->buffirst = 0;
317 		dComm->bufnext = 0;
318 		/* store send time for ping calculations */
319 		gettimeofday (&dComm->lastSnd, NULL);
320 		if (dComm->sndfirst < dComm->sndnext) {
321 			Dbg_Dgram ("sent frames [%lu,%lu]\n", (unsigned long)dComm->sndfirst,(unsigned long) dComm->sndnext - 1);
322 		}
323 		else {
324 			Dbg_Dgram ("sent pings\n");
325 		}
326 	}
327 	CurrentStatus (dComm);
328 	return XCR_OK;
329 }								/* WriteDgram */
330 
331 /*
332  * free the XBComm structure
333  */
334 static XBCommResult
DeleteDgram(XBComm * comm)335 DeleteDgram (XBComm * comm)
336 {
337 	XBCommDgram *dgram = (XBCommDgram *) comm;
338 	assert (dgram != NULL);
339 	CurrentStatus (dgram);
340 	if (NULL != dgram->snd) {
341 		Net_DeleteDatagram (dgram->snd);
342 	}
343 	(void)(dgram->infoFunc) (dgram, XBDI_CLOSE);
344 	CommFinish (&dgram->comm);
345 	free (dgram);
346 	Dbg_Dgram ("instance removed\n");
347 	return XCR_OK;
348 }								/* DeleteDgram */
349 
350 /*
351  * create datagram communication structure
352  */
353 XBComm *
Dgram_CommInit(XBCommDgram * dComm,XBCommType commType,XBSocket * pSocket,DgramPingFunc pingFunc,DgramInfoFunc infoFunc,DgramActionFunc actionFunc)354 Dgram_CommInit (XBCommDgram * dComm, XBCommType commType, XBSocket * pSocket,
355 				DgramPingFunc pingFunc, DgramInfoFunc infoFunc, DgramActionFunc actionFunc)
356 {
357 	assert (NULL != dComm);
358 	assert (NULL != pingFunc);
359 	assert (NULL != infoFunc);
360 	assert (NULL != actionFunc);
361 	/* set values */
362 	CommInit (&dComm->comm, commType, pSocket, ReadDgram, WriteDgram, DeleteDgram);
363 	dComm->snd = NULL;
364 	dComm->port = Net_LocalPort (pSocket);
365 	dComm->host = NULL;
366 	dComm->connected = XBFalse;
367 	dComm->maskbytes = 1;
368 	dComm->rcvfirst = 0;
369 	dComm->rcvnext = 0;
370 	dComm->buffirst = 0;
371 	dComm->bufnext = 0;
372 	dComm->sndfirst = 0;
373 	dComm->sndnext = 0;
374 	dComm->ignore = 0;
375 	dComm->queue = 0;
376 	dComm->expect = 0;
377 	dComm->pingFunc = pingFunc;
378 	dComm->infoFunc = infoFunc;
379 	dComm->actionFunc = actionFunc;
380 	dComm->lastSnd.tv_sec = 0;
381 	dComm->lastSnd.tv_usec = 0;
382 	dComm->lastRcv.tv_sec = 0;
383 	dComm->lastRcv.tv_usec = 0;
384 	memset (dComm->ppa, 0, sizeof (dComm->ppa));
385 	/* that's all */
386 	Dbg_Dgram ("created at local port %u\n", dComm->port);
387 	CurrentStatus (dComm);
388 	return &dComm->comm;
389 }								/* D2C_CreateComm */
390 
391 /*
392  * get port for client
393  */
394 unsigned short
Dgram_Port(const XBCommDgram * dComm)395 Dgram_Port (const XBCommDgram * dComm)
396 {
397 	/* sanity checks */
398 	assert (dComm != NULL);
399 	/* get value */
400 	return dComm->port;
401 }								/* D2C_Port */
402 
403 /*
404  * reset read/write parameters
405  */
406 void
Dgram_Reset(XBCommDgram * dComm)407 Dgram_Reset (XBCommDgram * dComm)
408 {
409 	assert (dComm != NULL);
410 	Dbg_Dgram ("resetting\n");
411 	/* clear any old datagrams */
412 	if (NULL != dComm->snd) {
413 		Dbg_Dgram ("clear buffer [%lu:%lu]\n", (unsigned long)dComm->buffirst, (unsigned long)dComm->bufnext - 1);
414 		Net_DeleteDatagram (dComm->snd);
415 		dComm->snd = NULL;
416 	}
417 	dComm->buffirst = 0;
418 	dComm->bufnext = 0;
419 	dComm->rcvfirst = 0;
420 	dComm->rcvnext = 0;
421 	dComm->sndfirst = 0;
422 	dComm->sndnext = 0;
423 	dComm->expect = 1;
424 	dComm->queue = 1;
425 	memset (dComm->ppa, 0, sizeof (dComm->ppa));
426 	CurrentStatus (dComm);
427 }								/* Dgram_Reset */
428 
429 /*
430  * set mask bytes
431  */
432 void
Dgram_SetMaskBytes(XBCommDgram * dComm,unsigned num)433 Dgram_SetMaskBytes (XBCommDgram * dComm, unsigned num)
434 {
435 	assert (dComm != NULL);
436 	assert (num > 0);
437 	assert (num < MAX_MASK_BYTES);
438 	dComm->maskbytes = num;
439 	Dbg_Dgram ("setting mask bytes to %u\n", num);
440 }								/* Dgram_SetMaskBytes */
441 
442 /*
443  * write a ping to buffer unless buffer is occupied
444  */
445 void
Dgram_SendPing(XBCommDgram * dComm)446 Dgram_SendPing (XBCommDgram * dComm)
447 {
448 	if (NULL == dComm->snd) {
449 		dComm->snd = Net_CreateDatagram (NULL, 0);
450 		Socket_RegisterWrite (CommSocket (&dComm->comm));
451 		Dbg_Dgram ("Queueing ping\n");
452 	}
453 	else {
454 		Dbg_Dgram ("Buffer occupied, discarding ping\n");
455 	}
456 }								/* Dgram_SendPing */
457 
458 /*
459  * write ping data to buffer unless buffer is occupied
460  */
461 void
Dgram_SendPingData(XBCommDgram * dComm,const int pingTime[])462 Dgram_SendPingData (XBCommDgram * dComm, const int pingTime[])
463 {
464 	size_t i;
465 	unsigned char pingData[2 * MAX_HOSTS];
466 
467 	if (NULL == dComm->snd) {
468 		assert (NULL != pingTime);
469 		/* setup buffer with ping times */
470 		pingData[0] = 0xFF & (GAME_TIME_PING);
471 		pingData[1] = 0xFF & (GAME_TIME_PING >> 8);
472 		for (i = 1; i < MAX_HOSTS; i++) {
473 			pingData[2 * i] = 0xFF & ((unsigned)pingTime[i]);
474 			pingData[2 * i + 1] = 0xFF & ((unsigned)pingTime[i] >> 8);
475 		}
476 		dComm->snd = Net_CreateDatagram (pingData, 2 * MAX_HOSTS);
477 		Socket_RegisterWrite (dComm->comm.socket);
478 		Dbg_Dgram ("Queued ping times\n");
479 	}
480 	else {
481 		Dbg_Dgram ("Buffer occupied, discarding ping times\n");
482 	}
483 }								/* Dgram_SendPingData */
484 
485 /*
486  * queue player actions or finish to buffer
487  */
488 static void
CreateBuffer(XBCommDgram * dComm,int first,int next)489 CreateBuffer (XBCommDgram * dComm, int first, int next)
490 {
491 	int i;
492 	size_t len;
493 
494 	/* sanity check */
495 	assert (dComm != NULL);
496 	/* clear any old datagrams */
497 	if (NULL != dComm->snd) {
498 		Dbg_Dgram ("update buffer [%lu:%lu]\n", (unsigned long)dComm->buffirst, (unsigned long)dComm->bufnext - 1);
499 		Net_DeleteDatagram (dComm->snd);
500 		dComm->snd = NULL;
501 	}
502 	/* set time stamp */
503 	memset (buffer, 0, sizeof (buffer));
504 	buffer[0] = 0xFF & first;
505 	buffer[1] = 0xFF & (first >> 8);
506 	/* fill buffer */
507 	len = 2;
508 	for (i = first; i < next; i++) {
509 		/* check if next action would overflow the buffer */
510 		if (dComm->ppa[i].numBytes + dComm->maskbytes + len > sizeof (buffer)) {
511 			break;
512 		}
513 		/* copy action mask to buffer */
514 		memcpy (buffer + len, dComm->ppa[i].mask, dComm->maskbytes);
515 		len += dComm->maskbytes;
516 		/* copy action data, if present */
517 		if (dComm->ppa[i].numBytes > 0) {
518 			memcpy (buffer + len, dComm->ppa[i].action, dComm->ppa[i].numBytes);
519 			len += dComm->ppa[i].numBytes;
520 		}
521 	}
522 	/* store current buffer parameters */
523 	dComm->buffirst = first;
524 	dComm->bufnext = i;
525 	/* prepare sending */
526 	Socket_RegisterWrite (dComm->comm.socket);
527 	assert (dComm->snd == NULL);
528 	dComm->snd = Net_CreateDatagram (buffer, len);
529 	CurrentStatus (dComm);
530 }								/* Dgram_SendPlayerAction */
531 
532 /*
533  * send player action to client
534  */
535 void
Dgram_SendPlayerAction(XBCommDgram * dComm,int gameTime,const PlayerAction * playerAction)536 Dgram_SendPlayerAction (XBCommDgram * dComm, int gameTime, const PlayerAction * playerAction)
537 {
538 	/* sanity check */
539 	assert (dComm != NULL);
540 	assert (gameTime < NUM_PLAYER_ACTION - 1);
541 	assert (playerAction != NULL);
542 	Dbg_Dgram ("queueing data for gt=%u\n", gameTime);
543 	/* pack data */
544 	PackPlayerAction (dComm->ppa + gameTime, playerAction, dComm->maskbytes);
545 	/* update buffer */
546 	CreateBuffer (dComm, dComm->queue, gameTime + 1);
547 }								/* Dgram_SendPlayerAction */
548 
549 /*
550  * acknowledge level finish
551  */
552 void
Dgram_SendFinish(XBCommDgram * dComm,int gameTime)553 Dgram_SendFinish (XBCommDgram * dComm, int gameTime)
554 {
555 	/* sanity check */
556 	assert (dComm != NULL);
557 	assert (gameTime < NUM_PLAYER_ACTION);
558 	Dbg_Dgram ("queueing finish for gt=%u\n", gameTime);
559 	/* pack data */
560 	dComm->ppa[gameTime].numBytes = 0;
561 	memcpy (dComm->ppa[gameTime].mask, PLAYER_MASK_FINISH, dComm->maskbytes);
562 	/* try to send it */
563 	CreateBuffer (dComm, dComm->queue, gameTime + 1);
564 }								/* Dgram_SendFinish */
565 
566 /*
567  * flush - resend last batch of data
568  */
569 XBBool
Dgram_Flush(XBCommDgram * dComm)570 Dgram_Flush (XBCommDgram * dComm)
571 {
572 	assert (dComm != NULL);
573 	Dbg_Dgram ("flushing\n");
574 	/* requeue last send */
575 	CreateBuffer (dComm, dComm->sndfirst, dComm->sndnext);
576 	return XBTrue;
577 }								/* Dgram_Flush */
578 
579 /*
580  * end of file com_dgram.c
581  */
582