File: snetserver.c

package info (click to toggle)
xscorch 0.2.1~pre2-2
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 4,848 kB
  • ctags: 3,514
  • sloc: ansic: 30,552; sh: 9,843; makefile: 472; perl: 16
file content (388 lines) | stat: -rw-r--r-- 12,937 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
/* $Header: /fridge/cvs/xscorch/snet/snetserver.c,v 1.19 2009-04-26 17:39:56 jacob Exp $ */
/*

   xscorch - snetserver.c     Copyright(c) 2000-2003 Justin David Smith
                              Copyright(c) 2001-2003 Jacob Luna Lundberg
   justins(at)chaos2.org      http://chaos2.org/
   jacob(at)chaos2.org        http://chaos2.org/~jacob

   Server control loop


   This program is free software; you can redistribute it and/or modify 
   it under the terms of the GNU General Public License as published by 
   the Free Software Foundation, version 2 of the License ONLY. 

   This program is distributed in the hope that it will be useful, 
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
   General Public License for more details.

   You should have received a copy of the GNU General Public License along
   with this program; if not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA

*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <snetint.h>

#include <sgame/saccessory.h>
#include <sgame/sgame.h>
#include <sgame/splayer.h>
#include <sgame/sstate.h>
#include <sgame/stankpro.h>
#include <sgame/sweapon.h>
#include <sgame/swindow.h>
#include <sutil/srand.h>

#include <libj/jstr/libjstr.h>
#include <snet/tcpnet/tcpnet.h>



static bool _sc_net_sync_all(sc_server *srv) {
/* _sc_net_sync_all

   I think you want to read snetclient.c (search for "_sync") before looking
   through this function, since it's not entirely obvious what all is going
   on here.  This code is a bit dense, but that's because we have to figure
   out _what_ the sync event is, then we have to figure out if any clients
   are not on the same event.

   Note we will send out the expected next random value for the server, so
   the client may check and see if they are actually on the correct value. 
   If the client is off, it is up to them to report the discrepancy back to
   the server and try to negotiate the error with us.  Otherwise, their
   orders will be run with respect to OUR map, and they will probably be
   very unhappy...  

   This returns true if a sync occurred (all clients were ready) and false
   if we are still waiting on a client to sync with us. */

   sc_packet packet;    /* New outgoing packet data */
   dword *p;            /* Random pointer into data */
   dword type = 0;      /* Sync event type - 0 == don't know */
   int rnd  = 0;        /* Next random value or bogus == wrong event */
   int i;               /* Iterate through client connections */

   for(i = 0; i < srv->connections; ++i) {
      /* Make sure every client is ready to sync */
      if(!SC_CONN_IS_SYNC(srv->clients[i])) return(false);

      /* If server, we can find out what the sync type is */
      if((SC_CONN_GET_ARG(srv->clients[i]) & SC_CONN_SYNC_SERV) != 0) {
         /* The server knows what type of event we're syncing */
         type = SC_CONN_GET_ARG(srv->clients[i]) & (~SC_CONN_SYNC_SERV);
      }
   }

   for(i = 0; i < srv->connections; ++i) {
      /* Sanity check to make sure everyone is syncing to the same event */
      rnd = game_rand_peek();
      if((SC_CONN_GET_ARG(srv->clients[i]) & (~SC_CONN_SYNC_SERV)) != type) {
         /* This client is not syncing to the right event.  We send back an
            invalid random value so the client will report ``random values
            out of alignment'' and hopefully call back to us to try to
            straighten things out. This is a hack of sorts, we'll handle
            this more gracefully later. */
         sc_net_set_error("svr_relay_orders", "Player was waiting for invalid sync");
         rnd = -1;
      }

      /* Prepare a packet to send out to the client */      
      if(sc_net_packet_init(&packet, SC_NET_SVR_SYNC_RESP, 2 * sizeof(dword))) {
         p = (dword *)packet.data;
         *p++ = htonl(type);
         *p++ = htonl(rnd);
         sc_net_send_packet_now(&srv->clients[i], &packet);
         SC_CONN_CLEAR_FLAGS(srv->clients[i], SC_CONN_WAIT_SYNC);
         sc_net_packet_release(&packet);
      }
   }

   /* IF we made it here, then something went right... */
   sc_net_set_info("svr_sync_all", "All clients sync; advancing state");
   return(true);

}



static bool _sc_net_relay_orders(const sc_config *c, sc_server *srv, const sc_packet *incoming, int connid) {
/* _sc_net_relay_orders
   Push orders from one player to all the other players. */

   const sc_player *pl;
   sdword playerid;
   dword turret;
   dword power;
   dword weapon;
   dword shield;
   dword x;
   dword y;
   sc_packet packet;
   dword *p;
   int size;
   int i;

   if(!sc_net_check_size(incoming, 7 * sizeof(dword), "relay_orders")) return(false);

   p = (dword *)incoming->data;
   playerid = ntohl(*p++);
   turret   = ntohl(*p++);
   power    = ntohl(*p++);
   weapon   = ntohl(*p++);
   shield   = ntohl(*p++);
   x        = ntohl(*p++);
   y        = ntohl(*p++);
   if(playerid < 0 || playerid >= c->numplayers) {
      sc_net_set_error("svr_relay_orders", "PlayerID received was invalid");
      return(false);
   }

   size = (1 + 7 * c->numplayers) * sizeof(dword);
   if(!sc_net_packet_init(&packet, SC_NET_SVR_ORDERS, size)) return(false);
   p = (dword *)packet.data;
   *p++ = htonl(c->numplayers);
   for(i = 0; i < c->numplayers; ++i) {
      *p++ = htonl(i);
      if(i == playerid) {
         *p++ = htonl(turret);
         *p++ = htonl(power);
         *p++ = htonl(weapon);
         *p++ = htonl(shield);
         *p++ = htonl(x);
         *p++ = htonl(y);
      } else {
         pl = c->players[i];
         *p++ = htonl(pl->turret);
         *p++ = htonl(pl->power);
         *p++ = htonl(pl->selweapon->ident);
         *p++ = htonl(pl->selshield->ident);
         *p++ = htonl(pl->x);
         *p++ = htonl(pl->y);
      }
   }

   for(i = 0; i < srv->connections; ++i) {
      if(i != connid && !SC_CONN_IS_DEAD(srv->clients[i])) {
         sc_net_send_packet_now(&srv->clients[i], &packet);
      }
   }

   sc_net_packet_release(&packet);
   return(true);

}



static void _sc_net_process_message(sc_config *c, sc_server *srv, sc_packet *incoming, int connid) {

   sc_connection *client = &srv->clients[connid];
   int i;

   switch(incoming->msg_type) {
   case SC_NET_CLI_DISCONNECT:
      SC_CONN_SET_FLAGS(*client, SC_CONN_QUIT);
      sc_net_set_info("process_message", "server: client has disconnected");
      break;
   case SC_NET_CLI_PLAYER_NAME:
      memcpy(c->players[connid]->name, incoming->data, SC_PLAYER_NAME_LENGTH);
      sc_net_set_info("process_message", "server: client gave us their name");
      sc_net_svr_send_config(c, srv);
      break;
   case SC_NET_CLI_SYNC_RQST:
      SC_CONN_SET_FLAGS(*client, SC_CONN_WAIT_SYNC);
      SC_CONN_SET_ARG(*client, ntohl(*(dword *)incoming->data));
      _sc_net_sync_all(srv);
      break;
   case SC_NET_CHAT:
   case SC_NET_SHIELDS:
   case SC_NET_BATTERY:
   case SC_NET_PLFLAGS:
   case SC_NET_INVENTORY:
   case SC_NET_PLAYER_STATE:
      /* Items that get relayed to the clients by the server. */
      for(i = 0; i < srv->connections; ++i) {
         if(i != connid && !SC_CONN_IS_DEAD(srv->clients[i])) {
            sc_net_send_packet_now(&srv->clients[i], incoming);
         }
      }
      break;
   case SC_NET_CLIENT_STATUS:
      sc_net_server_relay_status(srv, incoming, connid);
      break;
   case SC_NET_CLI_ORDERS:
      _sc_net_relay_orders(c, srv, incoming, connid);
      break;
   default:
      sc_net_set_error("process_message", "invalid packet type received by server");
      break;
   }

}



static void _sc_net_connect_request(sc_config *c, sc_server *srv, const sc_packet *incoming, int connid) {

   char versionstr[SC_NET_BUFFER_SIZE];
   char infomsg[SC_NET_BUFFER_SIZE];
   sc_connection *client;
   sc_packet reply;
   dword version;

   client = &srv->clients[connid];

   /* Make sure this actually looks like a connect request */
   if(incoming->msg_type != SC_NET_CLI_CONNECT) {
      sc_net_set_error("connect_request", "Incoming packet from unknown source, not a connect request");
      return;
   }

   /* From this point, we assume a connection from an xscorch client.
      Any failures from this point should be sent to them. */

   /* Check the version number data (should be in flags) */
   version = ntohl(*(dword *)incoming->data);
   if(version < SC_NET_MIN_VERSION) {
      sc_net_send_message(client, SC_NET_SVR_REJECTED, "Client version is too old");
      sc_net_set_error("connect_request", "Incoming packet had protocol version that was too ancient");
      return;
   }
   if(version > SC_NET_VERSION) {
      sc_net_send_message(client, SC_NET_SVR_REJECTED, "Client version is too new");
      sc_net_set_error("connect_request", "Incoming packet had protocol version that was too recent");
      return;
   }

   /* notify client they are on */
   sc_net_version_info(versionstr, sizeof(versionstr));
   if(!sc_net_packet_init(&reply, SC_NET_SVR_ACCEPTED, strlenn(versionstr) + sizeof(dword))) return;
   *(dword *)reply.data = htonl(SC_NET_VERSION);
   memcpy(reply.data + sizeof(dword), versionstr, strlenn(versionstr));
   sc_net_send_packet_now(client, &reply);

   /* Informative */
   memcpy(versionstr, incoming->data + sizeof(dword), incoming->data_size - sizeof(dword));
   versionstr[incoming->data_size - sizeof(dword)] = '\0';
   sbprintf(infomsg, sizeof(infomsg), "Client accepted, version \"%s\"", versionstr);
   sc_net_set_info("connect_request", infomsg);

   /* Send the game configuration */
   sc_net_svr_send_config(c, srv);

   /* Update server setup */
   sc_net_packet_release(&reply);
   SC_CONN_CLEAR_FLAGS(*client, SC_CONN_NEED_ACCEPT);

}



bool sc_net_server_handle_packet(sc_config *c, void *parm, sc_packet *p) {

   sc_server *srv = (sc_server *)parm;
   int connid = srv->current;

   /* Check if this player is officially connected? */
   if(srv->clients[connid].flags & SC_CONN_NEED_ACCEPT) {
      /* This is hopefully a connection request/new player */
      _sc_net_connect_request(c, srv, p, connid);
   } else {
      /* This is an existing connection, process as event */
      _sc_net_process_message(c, srv, p, connid);
   } /* New player? */
   return(true);

}



static void _sc_net_incoming_connect(sc_config *c, sc_server *srv, int socket, const addr *fromaddr, int fromaddrsize) {

   sc_connection *client;
   sc_player *p;

   /* Make sure socket is nonblocking */
   sc_net_set_nonblocking(socket);

   /* Check that we have space to accept this connection into */
   if(srv->connections >= SC_MAX_PLAYERS) {
      sc_net_shutdown(&socket);
      sc_net_set_error("connect_request", "Incoming connection rejected because server is full");
      return;
   }

   /* Check that a game is not in progress */
   if((c->game->state & SC_STATE_OPTIONS_FLAG) == 0) {
      sc_net_shutdown(&socket);
      sc_net_set_error("connect_request", "Incoming connection rejected because game is already running");
      return;
   }

   /* Register the connection with the TCP NET packet engine. */
   if(!tn_instantiate(&srv->clients[srv->connections].connection, socket)) {
      sc_net_shutdown(&socket);
      sc_net_set_error("connect_request", "Unable to register socket with packet engine");
      return;
   }

   /* Below we set up the connection so we can recieve a configuration packet from the client. */

   /* Add this player to the list of active players */
   client = &srv->clients[srv->connections++];
   client->socket = socket;
   memcpy(&client->address, fromaddr, fromaddrsize);
   client->flags = SC_CONN_SVR_IFLAGS;

   /* Setup player data */
   p = c->players[c->numplayers];
   sbprintf(p->name, sizeof(p->name), "Network Player %d", c->numplayers);
   p->aitype = SC_AI_NETWORK;
   p->tank = sc_tank_profile_lookup(c->tanks, 0);
   ++c->numplayers;

}



bool sc_net_server_run(sc_config *c, sc_server *srv) {

   sc_connection *client;
   addr address;
   socklen_t addrsize;
   int connid;
   int socket;
   bool tryagain;

   /* Sanity checks */
   if(c == NULL || srv == NULL) return(false);

   /* Look for new incoming connections */
   do {
      addrsize = sizeof(addr);
      socket = accept(srv->linein, (struct sockaddr *)&address, &addrsize);
      if(socket != -1) _sc_net_incoming_connect(c, srv, socket, &address, addrsize);
   } while(socket != -1);

   /* Check for incoming packets from existing clients */
   for(connid = 0; connid < srv->connections; ++connid) {
      srv->current = connid;
      client = &srv->clients[connid];
      tryagain = !SC_CONN_IS_DEAD(*client);
      while(tryagain) {
         tryagain = sc_net_recv_packet(client, c, (void *)srv, &sc_net_server_handle_packet);
         if(SC_CONN_IS_DEAD(*client)) tryagain = false;
      }
   }

   return(true);

}