1: <?php
2: /**
3: * This file is part of GameQ.
4: *
5: * GameQ is free software; you can redistribute it and/or modify
6: * it under the terms of the GNU Lesser General Public License as published by
7: * the Free Software Foundation; either version 3 of the License, or
8: * (at your option) any later version.
9: *
10: * GameQ is distributed in the hope that it will be useful,
11: * but WITHOUT ANY WARRANTY; without even the implied warranty of
12: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13: * GNU Lesser General Public License for more details.
14: *
15: * You should have received a copy of the GNU Lesser General Public License
16: * along with this program. If not, see <http://www.gnu.org/licenses/>.
17: *
18: *
19: */
20:
21: namespace GameQ;
22:
23: /**
24: * Handles the core functionality for the protocols
25: *
26: * @SuppressWarnings(PHPMD.NumberOfChildren)
27: *
28: * @author Austin Bischoff <austin@codebeard.com>
29: */
30: abstract class Protocol
31: {
32: /**
33: * Constants for class states
34: */
35: const STATE_TESTING = 1;
36:
37: const STATE_BETA = 2;
38:
39: const STATE_STABLE = 3;
40:
41: const STATE_DEPRECATED = 4;
42:
43: /**
44: * Constants for packet keys
45: */
46: const PACKET_ALL = 'all'; // Some protocols allow all data to be sent back in one call.
47:
48: const PACKET_BASIC = 'basic';
49:
50: const PACKET_CHALLENGE = 'challenge';
51:
52: const PACKET_CHANNELS = 'channels'; // Voice servers
53:
54: const PACKET_DETAILS = 'details';
55:
56: const PACKET_INFO = 'info';
57:
58: const PACKET_PLAYERS = 'players';
59:
60: const PACKET_STATUS = 'status';
61:
62: const PACKET_RULES = 'rules';
63:
64: const PACKET_VERSION = 'version';
65:
66: /**
67: * Transport constants
68: */
69: const TRANSPORT_UDP = 'udp';
70:
71: const TRANSPORT_TCP = 'tcp';
72:
73: const TRANSPORT_SSL = 'ssl';
74:
75: const TRANSPORT_TLS = 'tls';
76:
77: /**
78: * Short name of the protocol
79: *
80: * @var string
81: */
82: protected $name = 'unknown';
83:
84: /**
85: * The longer, fancier name for the protocol
86: *
87: * @var string
88: */
89: protected $name_long = 'unknown';
90:
91: /**
92: * The difference between the client port and query port
93: *
94: * @var int
95: */
96: protected $port_diff = 0;
97:
98: /**
99: * The transport method to use to actually send the data
100: * Default is UDP
101: *
102: * @var string
103: */
104: protected $transport = self::TRANSPORT_UDP;
105:
106: /**
107: * The protocol type used when querying the server
108: *
109: * @var string
110: */
111: protected $protocol = 'unknown';
112:
113: /**
114: * Holds the valid packet types this protocol has available.
115: *
116: * @var array
117: */
118: protected $packets = [];
119:
120: /**
121: * Holds the response headers and the method to use to process them.
122: *
123: * @var array
124: */
125: protected $responses = [];
126:
127: /**
128: * Holds the list of methods to run when parsing the packet response(s) data. These
129: * methods should provide all the return information.
130: *
131: * @var array
132: */
133: protected $process_methods = [];
134:
135: /**
136: * The packet responses received
137: *
138: * @var array
139: */
140: protected $packets_response = [];
141:
142: /**
143: * Holds the instance of the result class
144: *
145: * @var null
146: */
147: protected $result = null;
148:
149: /**
150: * Options for this protocol
151: *
152: * @var array
153: */
154: protected $options = [];
155:
156: /**
157: * Define the state of this class
158: *
159: * @var int
160: */
161: protected $state = self::STATE_STABLE;
162:
163: /**
164: * Holds specific normalize settings
165: *
166: * @todo: Remove this ugly bulk by moving specific ones to their specific game(s)
167: *
168: * @var array
169: */
170: protected $normalize = [
171: // General
172: 'general' => [
173: // target => source
174: 'dedicated' => [
175: 'listenserver',
176: 'dedic',
177: 'bf2dedicated',
178: 'netserverdedicated',
179: 'bf2142dedicated',
180: 'dedicated',
181: ],
182: 'gametype' => ['ggametype', 'sigametype', 'matchtype'],
183: 'hostname' => ['svhostname', 'servername', 'siname', 'name'],
184: 'mapname' => ['map', 'simap'],
185: 'maxplayers' => ['svmaxclients', 'simaxplayers', 'maxclients', 'max_players'],
186: 'mod' => ['game', 'gamedir', 'gamevariant'],
187: 'numplayers' => ['clients', 'sinumplayers', 'num_players'],
188: 'password' => ['protected', 'siusepass', 'sineedpass', 'pswrd', 'gneedpass', 'auth', 'passsord'],
189: ],
190: // Indvidual
191: 'player' => [
192: 'name' => ['nick', 'player', 'playername', 'name'],
193: 'kills' => ['kills'],
194: 'deaths' => ['deaths'],
195: 'score' => ['kills', 'frags', 'skill', 'score'],
196: 'ping' => ['ping'],
197: ],
198: // Team
199: 'team' => [
200: 'name' => ['name', 'teamname', 'team_t'],
201: 'score' => ['score', 'score_t'],
202: ],
203: ];
204:
205: /**
206: * Quick join link
207: *
208: * @var null|string
209: */
210: protected $join_link = null;
211:
212: /**
213: * @param array $options
214: */
215: public function __construct(array $options = [])
216: {
217: // Set the options for this specific instance of the class
218: $this->options = $options;
219: }
220:
221: /**
222: * String name of this class
223: *
224: * @return string
225: */
226: public function __toString()
227: {
228: return $this->name;
229: }
230:
231: /**
232: * Get the port difference between the server's client (game) and query ports
233: *
234: * @return int
235: */
236: public function portDiff()
237: {
238: return $this->port_diff;
239: }
240:
241: /**
242: * "Find" the query port based off of the client port and port_diff
243: *
244: * This method is meant to be overloaded for more complex maths or lookup tables
245: *
246: * @param int $clientPort
247: *
248: * @return int
249: */
250: public function findQueryPort($clientPort)
251: {
252: return $clientPort + $this->port_diff;
253: }
254:
255: /**
256: * Return the join_link as defined by the protocol class
257: *
258: * @return null|string
259: */
260: public function joinLink()
261: {
262: return $this->join_link;
263: }
264:
265: /**
266: * Short (callable) name of this class
267: *
268: * @return string
269: */
270: public function name()
271: {
272: return $this->name;
273: }
274:
275: /**
276: * Long name of this class
277: *
278: * @return string
279: */
280: public function nameLong()
281: {
282: return $this->name_long;
283: }
284:
285: /**
286: * Return the status of this Protocol Class
287: *
288: * @return int
289: */
290: public function state()
291: {
292: return $this->state;
293: }
294:
295: /**
296: * Return the protocol property
297: *
298: * @return string
299: */
300: public function getProtocol()
301: {
302: return $this->protocol;
303: }
304:
305: /**
306: * Get/set the transport type for this protocol
307: *
308: * @param string|null $type
309: *
310: * @return string
311: */
312: public function transport($type = null)
313: {
314: // Act as setter
315: if (!is_null($type)) {
316: $this->transport = $type;
317: }
318:
319: return $this->transport;
320: }
321:
322: /**
323: * Set the options for the protocol call
324: *
325: * @param array $options
326: *
327: * @return array
328: */
329: public function options($options = [])
330: {
331: // Act as setter
332: if (!empty($options)) {
333: $this->options = $options;
334: }
335:
336: return $this->options;
337: }
338:
339:
340: // Packet Section
341:
342: /**
343: * Return specific packet(s)
344: *
345: * @param array $type
346: *
347: * @return array
348: */
349: public function getPacket($type = [])
350: {
351: $packets = [];
352:
353:
354: // We want an array of packets back
355: if (is_array($type) && !empty($type)) {
356: // Loop the packets
357: foreach ($this->packets as $packet_type => $packet_data) {
358: // We want this packet
359: if (in_array($packet_type, $type)) {
360: $packets[$packet_type] = $packet_data;
361: }
362: }
363: } elseif ($type == '!challenge') {
364: // Loop the packets
365: foreach ($this->packets as $packet_type => $packet_data) {
366: // Dont want challenge packets
367: if ($packet_type != self::PACKET_CHALLENGE) {
368: $packets[$packet_type] = $packet_data;
369: }
370: }
371: } elseif (is_string($type)) {
372: // Return specific packet type
373: $packets = $this->packets[$type];
374: } else {
375: // Return all packets
376: $packets = $this->packets;
377: }
378:
379: // Return the packets
380: return $packets;
381: }
382:
383: /**
384: * Get/set the packet response
385: *
386: * @param array $response
387: *
388: * @return array
389: */
390: public function packetResponse(array $response = [])
391: {
392: // Act as setter
393: if (!empty($response)) {
394: $this->packets_response = $response;
395: }
396:
397: return $this->packets_response;
398: }
399:
400:
401: // Challenge section
402:
403: /**
404: * Determine whether or not this protocol has a challenge needed before querying
405: *
406: * @return bool
407: */
408: public function hasChallenge()
409: {
410: return (isset($this->packets[self::PACKET_CHALLENGE]) && !empty($this->packets[self::PACKET_CHALLENGE]));
411: }
412:
413: /**
414: * Parse the challenge response and add it to the buffer items that need it.
415: * This should be overloaded by extending class
416: *
417: * @codeCoverageIgnore
418: * @SuppressWarnings(PHPMD.UnusedFormalParameter)
419: *
420: * @param \GameQ\Buffer $challenge_buffer
421: *
422: * @return bool
423: */
424: public function challengeParseAndApply(Buffer $challenge_buffer)
425: {
426: return true;
427: }
428:
429: /**
430: * Apply the challenge string to all the packets that need it.
431: *
432: * @param string $challenge_string
433: *
434: * @return bool
435: */
436: protected function challengeApply($challenge_string)
437: {
438: // Let's loop through all the packets and append the challenge where it is needed
439: foreach ($this->packets as $packet_type => $packet) {
440: $this->packets[$packet_type] = sprintf($packet, $challenge_string);
441: }
442:
443: return true;
444: }
445:
446: /**
447: * Get the normalize settings for the protocol
448: *
449: * @return array
450: */
451: public function getNormalize()
452: {
453: return $this->normalize;
454: }
455:
456: // General
457:
458: /**
459: * Generic method to allow protocol classes to do work right before the query is sent
460: *
461: * @codeCoverageIgnore
462: * @SuppressWarnings(PHPMD.UnusedFormalParameter)
463: *
464: * @param \GameQ\Server $server
465: */
466: public function beforeSend(Server $server)
467: {
468: }
469:
470: /**
471: * Method called to process query response data. Each extending class has to have one of these functions.
472: *
473: * @return mixed
474: */
475: abstract public function processResponse();
476: }
477: