1
1
import struct
2
2
import enum
3
3
import socket
4
- import time
4
+ import select
5
5
6
6
class RconPacketTypes (enum .IntEnum ):
7
7
SERVERDATA_AUTH = 3
@@ -10,53 +10,96 @@ class RconPacketTypes(enum.IntEnum):
10
10
SERVERDATA_RESPONSE_VALUE = 0
11
11
12
12
13
- def _request_id ():
14
- return int (time .time ())
13
+ class RconPacket :
14
+ __id__ = 0
15
+ id : int
16
+ type : int
17
+ payload : str
15
18
16
- def _write_packet (sock : socket .socket , _type : int , payload : str ):
17
- # Prepare packet
18
- req_id = _request_id ()
19
- packet = struct .pack ('<ii{}s2s' .format (len (payload )), req_id , _type , payload .encode ('ascii' ), b'\0 \0 ' )
20
- header = struct .pack ('<i' , len (packet ))
19
+ def __new__ (cls , * args , ** kwargs ):
20
+ obj = object .__new__ (cls )
21
+ if len (args ) == 1 :
22
+ req_id , _type , payload = struct .unpack_from ('<ii{}s' .format (len (args [0 ]) - 10 ), args [0 ])
23
+ setattr (obj , 'id' , req_id )
24
+ setattr (obj , 'type' , _type )
25
+ setattr (obj , 'payload' , payload .decode ('ascii' ))
26
+ elif len (kwargs ) >= 2 :
27
+ setattr (obj , 'id' , kwargs .get ('id' , cls .get_id ()))
28
+ setattr (obj , 'type' , kwargs ['type' ])
29
+ setattr (obj , 'payload' , kwargs ['payload' ])
30
+ else :
31
+ raise Exception ('Wrong arguments' )
21
32
22
- # Send packet
23
- data_sent = sock .send (header + packet )
24
- return req_id , data_sent
33
+ return obj
25
34
26
- def _read_packet (sock : socket .socket ):
27
- # Recv data
28
- length_bytes = sock .recv (4 )
29
- length = struct .unpack ('<i' , length_bytes )[0 ]
30
- data = bytes ()
35
+ @classmethod
36
+ def get_id (cls ):
37
+ cls .__id__ += 1
38
+ return cls .__id__
31
39
32
- # Ensure that all the data was read
33
- while length > len (data ):
34
- data += sock .recv (length - len (data ))
40
+ @property
41
+ def data (self ):
42
+ data = struct .pack ('<ii{}s2s' .format (len (self .payload )), self .id , self .type , self .payload .encode ('ascii' ), b'\0 \0 ' )
43
+ return data
35
44
36
- # Parse data
37
- r_req_id , r_type , payload = struct .unpack ('<ii{}s' .format (len (data ) - 8 ), data )
38
- return r_req_id , r_type , payload
39
45
40
- def authenticate (sock : socket .socket , password : str ):
41
- req_id = _write_packet (sock , RconPacketTypes .SERVERDATA_AUTH , password )[0 ]
46
+ class RconClient :
42
47
43
- r_req_id , r_type , _ = _read_packet (sock )
48
+ def __init__ (self , host : str , port : int , password : str ):
49
+ self .__sock = socket .socket (socket .AF_INET , socket .SOCK_STREAM )
50
+ self .__host = (host , port )
51
+ self .__password = password
44
52
45
- if r_type != RconPacketTypes .SERVERDATA_AUTH_RESPONSE or r_req_id == - 1 :
46
- raise Exception ('Authentication failed' )
53
+ def _recv (self ):
54
+ if select .select ([self .__sock ], [], [], 2 )[0 ]:
55
+ with self .__sock .makefile ('rb' ) as stream :
56
+ packet_length = struct .unpack ('<i' , stream .read (4 ))[0 ]
57
+
58
+ data = b''
59
+ while len (data ) < packet_length :
60
+ _data = stream .read (packet_length - len (data ))
61
+ if _data :
62
+ data += _data
47
63
48
- def run_command (sock : socket .socket , command : str ):
49
- req_id = _write_packet (sock , RconPacketTypes .SERVERDATA_EXECOMMAND , command )[0 ]
64
+ return RconPacket (data )
50
65
51
- fpayload = bytes ()
52
- try :
53
- # Receive message until no more
54
- while 1 :
55
- r_req_id , r_type , payload = _read_packet (sock )
66
+ def _send (self , packet : RconPacket ):
67
+ if select .select ([], [self .__sock ], [], 2 )[1 ]:
68
+ with self .__sock .makefile ('wb' ) as stream :
69
+ stream .write (struct .pack ('<i' , len (packet .data )))
70
+ stream .write (packet .data )
71
+
72
+ def connect (self ):
73
+ self .__sock .connect (self .__host )
74
+ self .__sock .setblocking (False )
75
+ self ._authenticate ()
76
+
77
+ def _authenticate (self ):
78
+ packet = RconPacket (type = RconPacketTypes .SERVERDATA_AUTH , payload = self .__password )
79
+ self ._send (packet )
56
80
57
- if r_type != RconPacketTypes .SERVERDATA_RESPONSE_VALUE or r_req_id != req_id :
58
- raise Exception ('Error occured while executing a command' )
81
+ r_packet = self ._recv ()
59
82
60
- fpayload += payload
61
- finally :
62
- return fpayload .decode ('ascii' )
83
+ if r_packet .type != RconPacketTypes .SERVERDATA_AUTH_RESPONSE or r_packet .id == - 1 :
84
+ raise Exception ('Authentication Failed !' )
85
+
86
+ def run_cmd (self , cmd : str ):
87
+ packet = RconPacket (type = RconPacketTypes .SERVERDATA_EXECOMMAND , payload = cmd )
88
+ self ._send (packet )
89
+
90
+ response = ''
91
+ try :
92
+ while 1 :
93
+ r_packet = self ._recv ()
94
+ if r_packet is None :
95
+ break
96
+
97
+ if r_packet .type != RconPacketTypes .SERVERDATA_RESPONSE_VALUE or r_packet .id != packet .id :
98
+ raise Exception ('Error while getting the response' )
99
+
100
+ response += r_packet .payload
101
+ finally :
102
+ return response
103
+
104
+ def close (self ):
105
+ self .__sock .close ()
0 commit comments