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