1 /*
2  * file com_dg_client.c - send datagrams to client
3  *
4  * $Id: com_dg_client.c,v 1.17 2006/02/18 21:40:02 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 CLIENT_UDP_PORT(id) (16168+(id))
30 
31 /*
32  * local types
33  */
34 typedef struct
35 {
36 	XBCommDgram dgram;
37 	unsigned id;
38 	long ping;
39 } XBCommDgramClient;
40 
41 /*
42  * local variables
43  */
44 static XBCommDgramClient *commList[MAX_HOSTS] = {
45 	/* this entry is never used (server) */
46 	NULL,
47 	/* up to 5 clients can connect */
48 	NULL, NULL, NULL, NULL, NULL,
49 };
50 static int commCount = 0;
51 
52 /*******************
53  * local functions *
54  *******************/
55 
56 /*
57  * calculate difference for two timevals
58  */
59 static void
DiffTimeVal(struct timeval * diff,const struct timeval * a,const struct timeval * b)60 DiffTimeVal (struct timeval *diff, const struct timeval *a, const struct timeval *b)
61 {
62 	assert (NULL != diff);
63 	assert (NULL != a);
64 	assert (NULL != b);
65 
66 	diff->tv_sec = a->tv_sec - b->tv_sec;
67 	if (a->tv_usec < b->tv_usec) {
68 		diff->tv_usec = 1000000 + a->tv_usec - b->tv_usec;
69 		diff->tv_sec--;
70 	}
71 	else {
72 		diff->tv_usec = a->tv_usec - b->tv_usec;
73 	}
74 }								/* DiffTimeVal */
75 
76 /********************
77  * polling function *
78  ********************/
79 
80 /*
81  * polling for datagram connections
82  */
83 static void
PollDatagram(const struct timeval * tv)84 PollDatagram (const struct timeval *tv)
85 {
86 	int i, j;
87 	struct timeval dtSnd;
88 	struct timeval dtRcv;
89 	XBBool initPingTime = XBFalse;
90 	int pingTime[MAX_HOSTS];
91 
92 	assert (NULL != tv);
93 	for (i = 1; i < MAX_HOSTS; i++) {
94 		if (NULL != commList[i] && commList[i]->dgram.connected) {
95 			/* when was last datagram send */
96 			DiffTimeVal (&dtSnd, tv, &commList[i]->dgram.lastSnd);
97 			DiffTimeVal (&dtRcv, tv, &commList[i]->dgram.lastRcv);
98 			/* send a ping to client, when no datagram was send for over 500ms */
99 			if (dtSnd.tv_sec >= 1 || dtSnd.tv_usec > 500000) {
100 				if (!initPingTime) {
101 					pingTime[0] = -1;
102 					for (j = 1; j < MAX_HOSTS; j++) {
103 						pingTime[j] = commList[j] ? commList[j]->ping : -1;
104 					}
105 					initPingTime = XBTrue;
106 				}
107 				Dgram_SendPingData (&commList[i]->dgram, pingTime);
108 			}
109 			/* check last chance to send datagram */
110 			if (0 != commList[i]->dgram.lastRcv.tv_sec && dtRcv.tv_sec > LINK_LOST) {
111 				/* inform application */
112 				Dbg_D2C ("client %u timed out\n", i);
113 				Server_DgramEvent (i, XBSC_Timeout);
114 			}
115 		}
116 	}
117 }								/* PollDatagram */
118 
119 /************
120  * handlers *
121  ************/
122 
123 /*
124  * server received ping or times from client (times are ignored)
125  */
126 static void
ReceivePing(XBCommDgram * dgram,unsigned clientID,unsigned short pingTime)127 ReceivePing (XBCommDgram * dgram, unsigned clientID, unsigned short pingTime)
128 {
129 	struct timeval tvPing;
130 	XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
131 
132 	/* server reacts to empty pings only */
133 	if (0 == clientID) {
134 		assert (dComm != NULL);
135 		DiffTimeVal (&tvPing, &dgram->lastRcv, &dgram->lastSnd);
136 		dComm->ping = 1000L * tvPing.tv_sec + (tvPing.tv_usec) / 1000L;
137 		Dbg_D2C ("ping from client %u = %lu ms\n", dComm->id, dComm->ping);
138 		/* inform application */
139 		Server_ReceivePing (dComm->id, dComm->ping);
140 	}
141 }								/* ReceivePing */
142 
143 /*
144  * server encountered some event while parsing
145  * return XBTrue will trigger delete handler
146  */
147 static XBBool
ReceiveInfo(XBCommDgram * dgram,XBDgramInfo info)148 ReceiveInfo (XBCommDgram * dgram, XBDgramInfo info)
149 {
150 	XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
151 	assert (dComm != NULL);
152 	switch (info) {
153 	case XBDI_LOSS:
154 		Dbg_D2C ("future frames from client %u\n", dComm->id);
155 		return (Server_DgramEvent (dComm->id, XBSC_GameTime));
156 	case XBDI_PARSED:
157 		Dbg_D2C ("Frames parsed [%lu,%lu]\n", (unsigned long)dgram->rcvfirst, (unsigned long)dgram->rcvnext);
158 		dgram->queue = dgram->rcvnext;
159 		return XBFalse;
160 	case XBDI_IGNORE:
161 		Dbg_D2C ("gametime %lu from client %u ignored\n", (unsigned long)dgram->ignore, dComm->id);
162 		return XBFalse;
163 	case XBDI_FINISH:
164 		Dbg_D2C ("Receiving FINISH from client %u\n", dComm->id);
165 		Server_ReceiveFinish (dComm->id);
166 		return XBFalse;
167 	case XBDI_CONSUCC:
168 		Dbg_D2C ("udp connection established to client %u\n", dComm->id);
169 		return XBFalse;
170 	case XBDI_CONFAIL:
171 		Dbg_D2C ("failed to connect to client %u\n", dComm->id);
172 		return (Server_DgramEvent (dComm->id, XBSC_ConnFailed));
173 	case XBDI_WRITEERR:
174 		Dbg_D2C ("write error to client %u\n", dComm->id);
175 		return (Server_DgramEvent (dComm->id, XBSC_WriteError));
176 	case XBDI_CLOSE:
177 		Dbg_D2C ("udp to client %u removed\n", dComm->id);
178 		D2C_Clear (dComm->id);
179 		return (Server_DgramEvent (dComm->id, XBSC_DgramClosed));
180 	default:
181 		Dbg_D2C ("ignoring unknown dgram event (%u)\n", info);
182 		return XBFalse;
183 	}
184 }								/* ReceiveInfo */
185 
186 /*
187  * server received action from client
188  */
189 static void
ReceivePlayerAction(XBCommDgram * dgram,int gameTime,const PlayerAction * playerAction)190 ReceivePlayerAction (XBCommDgram * dgram, int gameTime, const PlayerAction * playerAction)
191 {
192 	XBCommDgramClient *dComm = (XBCommDgramClient *) dgram;
193 	assert (dComm != NULL);
194 	Dbg_D2C ("Receiving action from client %u at gt=%u\n", dComm->id, gameTime);
195 	Server_ReceivePlayerAction (dComm->id, gameTime, playerAction);
196 }								/* ReceivePlayerAction */
197 
198 /***************
199  * constructor *
200  ***************/
201 
202 /*
203  * create datagram connection to a game client
204  */
205 XBComm *
D2C_CreateComm(unsigned id,const char * localname,XBBool fixedPort)206 D2C_CreateComm (unsigned id, const char *localname, XBBool fixedPort)
207 {
208 	XBSocket *pSocket;
209 	/* sanity checks */
210 	assert (id > 0);
211 	assert (id < MAX_HOSTS);
212 	assert (commList[id] == NULL);
213 	/* create socket */
214 	pSocket = Net_BindUdp (localname, fixedPort ? CLIENT_UDP_PORT (id) : 0);
215 	if (NULL == pSocket) {
216 		Dbg_D2C ("failed to create socket to client %u\n", id);
217 		return NULL;
218 	}
219 	Dbg_D2C ("created socket to client %u on port %u\n", id, fixedPort ? CLIENT_UDP_PORT (id) : 0);
220 	/* create communication data structure */
221 	commList[id] = calloc (1, sizeof (*commList[id]));
222 	assert (NULL != commList[id]);
223 	/* set values */
224 	Dgram_CommInit (&commList[id]->dgram, COMM_DgClient, pSocket, ReceivePing, ReceiveInfo,
225 					ReceivePlayerAction);
226 	commList[id]->id = id;
227 	/* setup polling */
228 	if (0 == commCount) {
229 		GUI_AddPollFunction (PollDatagram);
230 	}
231 	commCount++;
232 	/* that's all */
233 	Dbg_D2C ("established handlers for client %u (poll #%u)\n", id, commCount);
234 	return &commList[id]->dgram.comm;
235 }								/* D2C_CreateComm */
236 
237 /**********************
238  * connect/disconnect *
239  **********************/
240 
241 /*
242  * connect to game client port
243  */
244 XBBool
D2C_Connect(unsigned id,const char * host,unsigned short port)245 D2C_Connect (unsigned id, const char *host, unsigned short port)
246 {
247 	/* sanity checks */
248 	assert (id > 0);
249 	assert (id < MAX_HOSTS);
250 	assert (commList[id] != NULL);
251 	assert (commList[id]->dgram.comm.socket != NULL);
252 	/* connect socket */
253 	if (port != 0) {
254 		/* connect to client address and port */
255 		commList[id]->dgram.connected =
256 			Net_ConnectUdp (commList[id]->dgram.comm.socket, host, port);
257 		Dbg_D2C ("connecting to client %u (%s:%u) : %s\n", id, host, port,
258 				 commList[id]->dgram.connected ? "ok" : "failed");
259 		return commList[id]->dgram.connected;
260 	}
261 	else {
262 		/* client uses NAT we wait for his first datagram */
263 		Dbg_D2C ("(NAT) setting expected host for client %u to %s\n", id, host);
264 		commList[id]->dgram.host = host;
265 		return XBTrue;
266 	}
267 }								/* D2C_Connect */
268 
269 /*
270  * clear connection data
271  */
272 void
D2C_Clear(unsigned id)273 D2C_Clear (unsigned id)
274 {
275 	assert (id > 0);
276 	assert (id < MAX_HOSTS);
277 	assert (commList[id] != NULL);
278 	commList[id] = NULL;
279 	/* disable polling */
280 	commCount--;
281 	if (0 == commCount) {
282 		GUI_SubtractPollFunction (PollDatagram);
283 	}
284 	Dbg_D2C ("Cleared connection to client %u\n", id);
285 }								/* ClearConnection */
286 
287 /*
288  * disconnect given client
289  */
290 void
D2C_Disconnect(unsigned id)291 D2C_Disconnect (unsigned id)
292 {
293 	assert (id > 0);
294 	assert (id < MAX_HOSTS);
295 	assert (commList[id] != NULL);
296 	/* we only need to shutdown the socket */
297 	Dbg_D2C ("disconnecting client %u\n", id);
298 	CommDelete (&commList[id]->dgram.comm);
299 }								/* D2C_Disconnect */
300 
301 /******************
302  * get local data *
303  ******************/
304 
305 /*
306  * get port for game client
307  */
308 unsigned short
D2C_Port(unsigned id)309 D2C_Port (unsigned id)
310 {
311 	return Dgram_Port (&commList[id]->dgram);
312 }								/* D2C_Port */
313 
314 /*
315  * is client connected ?
316  */
317 XBBool
D2C_Connected(unsigned id)318 D2C_Connected (unsigned id)
319 {
320 	assert (id < MAX_HOSTS);
321 	return (commList[id] != NULL);
322 }								/* D2C_Connected */
323 
324 /*
325  * last ping time
326  */
327 long
D2C_LastPing(unsigned id)328 D2C_LastPing (unsigned id)
329 {
330 	assert (id > 0);
331 	assert (id < MAX_HOSTS);
332 	assert (NULL != commList[id]);
333 	return commList[id]->ping;
334 }								/* D2C_Connected */
335 
336 /******************
337  * set local data *
338  ******************/
339 
340 /*
341  * reset communication after level start
342  */
343 void
D2C_Reset(unsigned id)344 D2C_Reset (unsigned id)
345 {
346 	assert (id > 0);
347 	assert (id < MAX_HOSTS);
348 	assert (commList[id] != NULL);
349 	Dbg_D2C ("resetting frames for client %u\n", id);
350 	Dgram_Reset (&commList[id]->dgram);
351 }								/* D2C_Reset */
352 
353 /*
354  * set mask bytes for all client connections
355  */
356 void
D2C_SetMaskBytes(unsigned num)357 D2C_SetMaskBytes (unsigned num)
358 {
359 	unsigned id;
360 	assert (num < MAX_MASK_BYTES);
361 	for (id = 1; id < MAX_HOSTS; id++) {
362 		if (D2C_Connected (id)) {
363 			Dgram_SetMaskBytes (&commList[id]->dgram, num);
364 		}
365 	}
366 	Dbg_D2C ("setting mask bytes to %u\n", num);
367 }								/* D2C_SetMaskBytes */
368 
369 /**************
370  * queue data *
371  **************/
372 
373 /*
374  * send player action to game client
375  */
376 void
D2C_SendPlayerAction(unsigned id,int gameTime,const PlayerAction * playerAction)377 D2C_SendPlayerAction (unsigned id, int gameTime, const PlayerAction * playerAction)
378 {
379 	assert (id > 0);
380 	assert (id < MAX_HOSTS);
381 	assert (commList[id] != NULL);
382 
383 	Dbg_D2C ("queueing actions for client %u at gt=%u\n", id, gameTime);
384 	Dgram_SendPlayerAction (&commList[id]->dgram, gameTime, playerAction);
385 }								/* D2C_SendPlayerAction */
386 
387 /*
388  * send finish level to game client
389  */
390 void
D2C_SendFinish(unsigned id,int gameTime)391 D2C_SendFinish (unsigned id, int gameTime)
392 {
393 	assert (id > 0);
394 	assert (id < MAX_HOSTS);
395 	assert (commList[id] != NULL);
396 
397 	Dbg_D2C ("queueing FINISH to client %u at gt=%u\n", id, gameTime);
398 	Dgram_SendFinish (&commList[id]->dgram, gameTime);
399 }								/* D2C_SendFinish */
400 
401 /*
402  * flush remaining datagrams
403  */
404 XBBool
D2C_Flush(unsigned id)405 D2C_Flush (unsigned id)
406 {
407 	assert (id > 0);
408 	assert (id < MAX_HOSTS);
409 	assert (commList[id] != NULL);
410 
411 	/* for server, last send acknowledged if last receive equals last sent */
412 	if (commList[id]->dgram.rcvnext == commList[id]->dgram.sndnext) {
413 		return XBFalse;
414 	}
415 	Dbg_D2C ("resending frames to client %u\n", id);
416 	return Dgram_Flush (&commList[id]->dgram);
417 }								/* D2C_Flush */
418 
419 /*
420  * end of file com_dg_client.c
421  */
422