class CoreStack: """The core stack.""" def __init__(self, debug=False, logger=None): self.__debug = debug self.logger = logger self.__socket = None # initialized after servers key is received. once set, # encryption is done self.__crypto = None # list containing QueuedPacket objects self.__packet_queues = [[], []] # ackid : [packet, last transmit tick] # a list is used because it needs to be assigned # (updating tick count) while the dictionary has # an open iterator self.__reliable_messages_in_transit = {} # (ack_id, packet) reliable messages that were received out of # order, packet does not include the reliable header self.__incoming_reliable_packets = [] self.__event_list = [] # the list of pending CoreEvent objects self.__last_packet_received_tick = get_tick_count_hs() # the packet handler functions self.__packet_handlers = { 0x03: self.__handle_reliable_message, 0x04: self.__handle_ack_packet, 0x05: self.__handle_sync_request_packet, 0x06: self.__handle_sync_response_packet, 0x07: self.__handle_disconnect_packet, 0x08: self.__handle_chunk, 0x09: self.__handle_chunk_end, 0x0A: self.__handle_huge_chunk, # xxx we could parse the reason out of this 0x0D: self.__handle_disconnect_packet, 0x0E: self.__handle_cluster_packet, } # the list of incoming chunk data that gets concatenated and # then handled self.__chunk_list = None self.__huge_chunk_data = None # for the external loop will be set to false if we disconnect # from server # ie !stopbot self.reconnect = True def reset_state(self): # for recop self.__socket = None # initialized after servers key is received. once set, # encryption is done self.__crypto = None # list containing QueuedPacket objects self.__packet_queues = [[], []] # ackid : [packet, last transmit tick] # a list is used because it needs to be assigned # (updating tick count) while the dictionary has an open iterator self.__reliable_messages_in_transit = {} # (ack_id, packet) reliable messages that were received out of # order, packet does not include the reliable header self.__incoming_reliable_packets = [] # the list of pending CoreEvent objects self.__event_list = [] def __log(self, level, message): if self.logger: self.logger.log(level, message) else: print (message) def __add_pending_event(self, core_event): """Adds CoreEvent to the list for handling.""" self.__event_list.append(core_event) def connect_to_server(self, server, port, newconn=1): """Connect to the server, otherwise raise an exception.""" self.__total_packets_sent = 0 self.__total_packets_received = 0 self.__next_outgoing_ack_id = 0 self.__next_incoming_ack_id = 0 self.__last_sync_response_received_tick = get_tick_count_hs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = get_tick_count_hs() self.__last_core_periodic_event_tick = get_tick_count_hs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) # changed for jython support doesnt seem to affect python self.__socket.setblocking(False) # self.__socket.settimeout(.01) self.__timeout_interval = 0.01 STATE_NONE = 0 STATE_CHALLENGE_RECEIVED = 1 STATE_CONNECTED = 2 client_key = (-random.randrange(1, sys.maxint)) server_challenge = 0 self.fc = FileChecksum() self.fc.generate_checksum_array(0) state = STATE_NONE timeouts = 0 while state != STATE_CONNECTED: if timeouts > 3: break # send queueing the packets directly is done here because # handshake packets are special if state == STATE_NONE: # send handshake initiation packet self.queue_packet(struct.pack( "<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queue_packet(struct.pack( "<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, get_tick_count_hs() )) # write sent packets to the network self.flush_outbound_queues() # read raw packet is used here because the packets # during handshake are a little bit special (such # as the 0x02 type containing key data) packet = self.__read_raw_packet(2) if packet is None: timeouts += 1 continue try: game_type, core_type = struct.unpack_from("<BB", packet) # default to bad state, unless we get an expected packet state = STATE_NONE if game_type == 0x00: if core_type == 0x05: # response to __sendClientKey() server_challenge, = struct.unpack_from("<i", packet, 2) state = STATE_CHALLENGE_RECEIVED elif core_type == 0x02: # success server_key, = struct.unpack_from("<i", packet, 2) self.__crypto = SubspaceEncryption( client_key, server_key) state = STATE_CONNECTED break except struct.error: pass if state != STATE_CONNECTED: self.__log(CRITICAL, "Unable to connect to server") self.__connected = False else: self.__connected = True def queue_packet(self, data, reliable=False, priority=PRIORITY_NORMAL): """Queue a packet to be sent.""" size = len(data) psize = size + (6 if reliable else 0) if psize < MAX_PACKET: self.__packet_queues[priority].append(QueuedPacket(data, reliable)) else: if size < (MAX_PACKET*100): # print "chunk total size:" + str(len(data)) # 6 for reliable header + 2 for packet header MAX_CHUNK = MAX_PACKET - 8 while(len(data) > 0): chunk_size = min(MAX_CHUNK, len(data)) remaining = len(data) - chunk_size if remaining > 0: packet = struct.pack("<BB", 0x00, 0x08) # print "Headpacket_size: %d Remaining Data:%d" % ( # chunk_size, remaining) else: packet = struct.pack("<BB", 0x00, 0x09) # print "Tailpacket_size: %d Remaining Data:%d" % ( # chunk_size, remaining) packet += data[0:chunk_size - 1] data = data[chunk_size:] self.__packet_queues[priority].append( QueuedPacket(packet, True)) else: # dont think this works when i putfile it seems to fail # if it gets so big as to use huge chunk print "huge chunk total size:" + str(size) # 6 for reliable header + 6 for packet header MAX_CHUNK = MAX_PACKET - 12 while(len(data) > 0): chunk_size = min(MAX_CHUNK, len(data)) packet = struct.pack("<BBI", 0x00, 0x0a, size) packet += data[0:chunk_size-1] data = data[chunk_size:] print "packet_size: %d Remaining Data:%d" % ( chunk_size, len(data)) self.__packet_queues[priority].append( QueuedPacket(packet, True)) def __generate_next_outbound_packet(self): """Extract packets from queues and return a buffer containing the next outbound packet that is to be sent on the network, if a packet can be built. """ h = self.__packet_queues[PRIORITY_HIGH] n = self.__packet_queues[PRIORITY_NORMAL] if len(h) == 0 and len(n) == 0: return None # determine which packets can be clustered, if any clusterable_packets = 0 try: for packet_list in [h, n]: for outgoing_packet in packet_list: if outgoing_packet.total_packet_size() > 255: raise StopIteration() clusterable_packets += 1 except StopIteration: pass data = None if clusterable_packets > 1: data = struct.pack("<BB", 0x00, 0x0E) reliable_allowed = True for packet_list in [h, n]: i = 0 for p in packet_list[:]: # plus 1 is for the data length in cluster if p.total_packet_size() <= 255 and \ len(data) + p.total_packet_size() + 1 <= \ MAX_PACKET: if p.reliable: # send reliable packet, can be disallowed due to # ordering concerns if reliable_allowed: packet = struct.pack( "<BBBI", p.total_packet_size(), 0x00, 0x03, self.__next_outgoing_ack_id & 0xFFFFFFFF ) + p.data # store off packet[1:] here so when its # retransmitted, it doesnt re-append the # 'size' field since only the data should be # resent self.__reliable_messages_in_transit[ self.__next_outgoing_ack_id] = \ [packet[1:], get_tick_count_hs()] self.__next_outgoing_ack_id += 1 data += packet packet_list[i] = None else: # send an unreliable packet data += struct.pack( '<B', p.total_packet_size()) + p.data packet_list[i] = None elif p.reliable: # this packet wont fit and it is reliable, so # dont allow following reliable packets to be # sent, because these need to be sent in order reliable_allowed = False i += 1 # remove None entries # opt: should these be # self.__packet_queues[PRIORITY_Xxx][:] = ... ? self.__packet_queues[PRIORITY_HIGH] = [ x for x in h if x is not None] self.__packet_queues[PRIORITY_NORMAL] = [ x for x in n if x is not None] else: # a single, non-cluster packet is being sent if len(h): p = h.pop(0) else: p = n.pop(0) data = p.data if p.reliable: # send packet with the reliable header prepended data = struct.pack( "<BBI", 0x00, 0x03, self.__next_outgoing_ack_id & 0xFFFFFFFF ) + data self.__reliable_messages_in_transit[ self.__next_outgoing_ack_id] = [data, get_tick_count_hs()] self.__next_outgoing_ack_id += 1 return data def flush_outbound_queues(self): """Flush outbound packet queues.""" while 1: if len(self.__packet_queues[PRIORITY_HIGH]) == 0 \ and len(self.__packet_queues[PRIORITY_NORMAL]) == 0: break # check to make sure outbound socket is writable rlist, wlist, xlist = select.select([], [self.__socket], [], 0) if len(wlist) == 0: break packet = self.__generate_next_outbound_packet() if packet: self.__send_raw_packet(packet) def __queue_ack_packet(self, ack_id): """Queue an ack packet.""" self.queue_packet(struct.pack("<BBI", 0x00, 0x04, ack_id & 0xFFFFFFFF)) def __queue_disconnect_packet(self): """Queue a disconnect packet.""" self.queue_packet(struct.pack("<BB", 0x00, 0x07)) def disconnect_from_server(self): """Disconnect from the server.""" self.__queue_disconnect_packet() self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) self.reconnect = False def should_reconnect(self): return self.reconnect def __handle_reliable_message(self, packet): """Handle an incoming reliable message.""" # this could be cleaner by adding to the incoming list, then # processing lists zero, core_type, ack_id = struct.unpack_from("<BBI", packet) self.__queue_ack_packet(ack_id) if ack_id == self.__next_incoming_ack_id: self.__next_incoming_ack_id += 1 self.__process_incoming_packet(packet[6:]) loop_again = True while loop_again: loop_again = False index = 0 for ack_packet_tuple in self.__incoming_reliable_packets: if ack_packet_tuple[0] == self.__next_incoming_ack_id: self.__incoming_reliable_packets.pop(index) self.__next_incoming_ack_id += 1 self.__process_incoming_packet(ack_packet_tuple[1]) loop_again = True break index += 1 elif ack_id > self.__next_incoming_ack_id: self.__incoming_reliable_packets.append((ack_id, packet[6:])) def __handle_chunk(self, packet): if self.__chunk_list is None: self.__chunk_list = [] # print "small chunk type: " + packet.encode('hex') self.__chunk_list.append(packet[2:]) def __handle_chunk_end(self, packet): if self.__chunk_list: self.__chunk_list.append(packet[2:]) self.__process_incoming_packet(''.join(self.__chunk_list)) self.__chunk_list = None # added by junky def __handle_huge_chunk(self, packet): type, type2, total_size = struct.unpack_from("<BBI", packet) if self.__huge_chunk_data is None: # new chunk self.__huge_chunk_data = packet[6:] print "huge chunk type: " + self.__huge_chunk_data.encode('hex') else: self.__huge_chunk_data += packet[6:] if len(self.__huge_chunk_data) == total_size: # packet complete self.__process_incoming_packet(self.__huge_chunk_data[:]) self.__huge_chunk_data = None def wait_for_event(self): """Wait for an event to occur. If no event is immediately available this call blocks. Must be called frequently for good performance.""" while self.__connected: self.__process_incoming_packets() self.flush_outbound_queues() # process tick event if necessary now = get_tick_count_hs() self.__event_tick_tick_accumulator += now - \ self.__last_wait_for_event_call_tick self.__last_wait_for_event_call_tick = now while self.__event_tick_tick_accumulator > EVENT_TICK_INTERVAL: self.__add_pending_event(CoreEvent(EVENT_TICK)) self.__event_tick_tick_accumulator -= EVENT_TICK_INTERVAL # create internal periodic core event if tick_diff(now, self.__last_core_periodic_event_tick) > \ EVENT_INTERNAL_CORE_PERIODIC_INTERVAL: self.__core_periodic_event() self.__last_core_periodic_event_tick = now # process pending events first if len(self.__event_list) > 0: # preprocess the event event = self.__event_list.pop(0) if event.type == EVENT_DISCONNECT: self.__connected = False return event # nothing left to do, wait on an event timeout = float( EVENT_TICK_INTERVAL - self.__event_tick_tick_accumulator ) / 100 # if there are no packets to be sent, just wait for a read # otherwise wait for a read or the outbound socket to be writable if len(self.__packet_queues[PRIORITY_HIGH]) == 0 and \ len(self.__packet_queues[PRIORITY_NORMAL]) == 0: select.select([self.__socket], [], [], timeout) else: select.select([self.__socket], [self.__socket], [], timeout) def __process_incoming_packets(self): """Process all incoming packets.""" # process incoming packets, if any exist while True: packet = self.__read_raw_packet(0) if packet is None: break self.__process_incoming_packet(packet) def __core_periodic_event(self): """This is called every 100ms""" # requeue reliable messages that havent been acked now = get_tick_count_hs() for ack_id, packet_last_transmit_tick_list in \ self.__reliable_messages_in_transit.iteritems(): if tick_diff(now, packet_last_transmit_tick_list[1]) > \ RELIABLE_RETRANSMIT_INTERVAL: self.queue_packet( packet_last_transmit_tick_list[0], False, PRIORITY_HIGH) packet_last_transmit_tick_list[1] = now if tick_diff(now, self.__last_packet_received_tick) >= \ NO_PACKET_RECEIVED_TIMEOUT_INTERVAL: self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) def __handle_cluster_packet(self, packet): packet = packet[2:] while len(packet): data_len, = struct.unpack_from("<B", packet) self.__process_incoming_packet(packet[1:data_len + 1]) packet = packet[data_len + 1:] def __handle_disconnect_packet(self, packet): self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) def __handle_sync_request_packet(self, packet): self.__queue_sync_response() def __handle_sync_response_packet(self, packet): self.__last_sync_response_received_tick = get_tick_count_hs() def __handle_ack_packet(self, packet): zero, type, ack_id = struct.unpack_from("<BBI", packet) self.__reliable_messages_in_transit.pop(ack_id, None) def __process_incoming_packet(self, packet): # process the incoming packet, etc try: type, = struct.unpack_from("<B", packet) if type == 0x00: # core packet handlers type, = struct.unpack_from("<B", packet, 1) if self.__debug: self.__log(DEBUG, "Handling Core Type: 0x%02X" % type) handler = self.__packet_handlers.get(type, None) if handler: handler(packet) else: self.__log( INFO, "wtf corestack type %i not handled" % type) else: if self.__debug: self.__log(DEBUG, "Handling Game Type: 0x%02X" % type) event = CoreEvent(EVENT_GAME_PACKET_RECEIVED) event.packet = packet self.__add_pending_event(event) except (IndexError, struct.error): self.__log(CRITICAL, "Error in packet processing") self.__log(CRITICAL, "Packet data: " + packet.encode('hex')) formatted_lines = traceback.format_exc().splitlines() for l in formatted_lines: self.__log(DEBUG, l) def __read_raw_packet(self, timeout): """Read a raw packet on the network, optionally blocking on the socket.""" rlist, wlist, xlist = select.select([self.__socket], [], [], timeout) if len(rlist) == 0: time.sleep(0.01) return None # try: # if self.__timeout_interval != timeout: # self.__timeout_interval = timeout # self.__socket.settimeout(self.__timeout_interval) # packet = self.__socket.recv(MAX_PACKET) # except socket.timeout as e: # #self.logger.info("timeout") # return packet = rlist[0].recv(MAX_PACKET) self.__total_packets_received += 1 # decrypt the packet if self.__crypto: try: type, = struct.unpack_from("<B", packet) begin_offset = 1 if type == 0x00: begin_offset = 2 packet = packet[:begin_offset] + self.__crypto.decrypt_data( packet[begin_offset:]) except (IndexError, struct.error): packet = None if packet: if self.__debug: self.__log(DEBUG, "Read:" + packet.encode('hex')) self.__last_packet_received_tick = get_tick_count_hs() return packet def _queue_sync_request(self): self.queue_packet( struct.pack( "<BBIII", 0x00, 0x05, get_tick_count_hs(), self.__total_packets_sent & 0xFFFFFFFF, self.__total_packets_received & 0xFFFFFFFF ), priority=PRIORITY_HIGH ) def __queue_sync_response(self): self.queue_packet( struct.pack( "<BBII", 0x00, 0x06, get_tick_count_hs(), tick_diff( get_tick_count_hs(), self.__last_sync_response_received_tick ) ), priority=PRIORITY_HIGH ) # def __handle_sync_response_packet(self, packet): # # packet contents are irrelevant # self.__last_sync_response_received_tick = get_tick_count_hs() def __queue_ack_message(self, ack_id): # not used anywhere? self.queue_packet(struct.pack( "<BBI", 0x00, 0x03, ack_id & 0xFFFFFFFF), priority=PRIORITY_HIGH) def __send_raw_packet(self, packet): if self.__debug: self.__log(DEBUG, "Sent:" + packet.encode('hex')) if self.__crypto: begin_offset = 1 if ord(packet[0]) == 0x00: begin_offset = 2 packet = packet[:begin_offset] + self.__crypto.encrypt_data( packet[begin_offset:]) self.__socket.sendall(packet) self.__total_packets_sent += 1
class CoreStack: """The core stack.""" def __init__(self, debug=False, logger=None): self.__debug = debug self.logger = logger self.__socket = None # initialized after servers key is received. once set, # encryption is done self.__crypto = None # list containing QueuedPacket objects self.__packet_queues = [[], []] # ackid : [packet, last transmit tick] # a list is used because it needs to be assigned # (updating tick count) while the dictionary has # an open iterator self.__reliable_messages_in_transit = {} # (ack_id, packet) reliable messages that were received out of # order, packet does not include the reliable header self.__incoming_reliable_packets = [] self.__event_list = [] # the list of pending CoreEvent objects self.__last_packet_received_tick = get_tick_count_hs() # the packet handler functions self.__packet_handlers = { 0x03: self.__handle_reliable_message, 0x04: self.__handle_ack_packet, 0x05: self.__handle_sync_request_packet, 0x06: self.__handle_sync_response_packet, 0x07: self.__handle_disconnect_packet, 0x08: self.__handle_chunk, 0x09: self.__handle_chunk_end, 0x0A: self.__handle_huge_chunk, # xxx we could parse the reason out of this 0x0D: self.__handle_disconnect_packet, 0x0E: self.__handle_cluster_packet, } # the list of incoming chunk data that gets concatenated and # then handled self.__chunk_list = None self.__huge_chunk_data = None # for the external loop will be set to false if we disconnect # from server # ie !stopbot self.reconnect = True def reset_state(self): # for recop self.__socket = None # initialized after servers key is received. once set, # encryption is done self.__crypto = None # list containing QueuedPacket objects self.__packet_queues = [[], []] # ackid : [packet, last transmit tick] # a list is used because it needs to be assigned # (updating tick count) while the dictionary has an open iterator self.__reliable_messages_in_transit = {} # (ack_id, packet) reliable messages that were received out of # order, packet does not include the reliable header self.__incoming_reliable_packets = [] # the list of pending CoreEvent objects self.__event_list = [] def __log(self, level, message): if self.logger: self.logger.log(level, message) else: print(message) def __add_pending_event(self, core_event): """Adds CoreEvent to the list for handling.""" self.__event_list.append(core_event) def connect_to_server(self, server, port, newconn=1): """Connect to the server, otherwise raise an exception.""" self.__total_packets_sent = 0 self.__total_packets_received = 0 self.__next_outgoing_ack_id = 0 self.__next_incoming_ack_id = 0 self.__last_sync_response_received_tick = get_tick_count_hs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = get_tick_count_hs() self.__last_core_periodic_event_tick = get_tick_count_hs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) # changed for jython support doesnt seem to affect python self.__socket.setblocking(False) # self.__socket.settimeout(.01) self.__timeout_interval = 0.01 STATE_NONE = 0 STATE_CHALLENGE_RECEIVED = 1 STATE_CONNECTED = 2 client_key = (-random.randrange(1, sys.maxint)) server_challenge = 0 self.fc = FileChecksum() self.fc.generate_checksum_array(0) state = STATE_NONE timeouts = 0 while state != STATE_CONNECTED: if timeouts > 3: break # send queueing the packets directly is done here because # handshake packets are special if state == STATE_NONE: # send handshake initiation packet self.queue_packet( struct.pack("<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queue_packet( struct.pack("<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, get_tick_count_hs())) # write sent packets to the network self.flush_outbound_queues() # read raw packet is used here because the packets # during handshake are a little bit special (such # as the 0x02 type containing key data) packet = self.__read_raw_packet(2) if packet is None: timeouts += 1 continue try: game_type, core_type = struct.unpack_from("<BB", packet) # default to bad state, unless we get an expected packet state = STATE_NONE if game_type == 0x00: if core_type == 0x05: # response to __sendClientKey() server_challenge, = struct.unpack_from("<i", packet, 2) state = STATE_CHALLENGE_RECEIVED elif core_type == 0x02: # success server_key, = struct.unpack_from("<i", packet, 2) self.__crypto = SubspaceEncryption( client_key, server_key) state = STATE_CONNECTED break except struct.error: pass if state != STATE_CONNECTED: self.__log(CRITICAL, "Unable to connect to server") self.__connected = False else: self.__connected = True def queue_packet(self, data, reliable=False, priority=PRIORITY_NORMAL): """Queue a packet to be sent.""" size = len(data) psize = size + (6 if reliable else 0) if psize < MAX_PACKET: self.__packet_queues[priority].append(QueuedPacket(data, reliable)) else: if size < (MAX_PACKET * 100): # print "chunk total size:" + str(len(data)) # 6 for reliable header + 2 for packet header MAX_CHUNK = MAX_PACKET - 8 while (len(data) > 0): chunk_size = min(MAX_CHUNK, len(data)) remaining = len(data) - chunk_size if remaining > 0: packet = struct.pack("<BB", 0x00, 0x08) # print "Headpacket_size: %d Remaining Data:%d" % ( # chunk_size, remaining) else: packet = struct.pack("<BB", 0x00, 0x09) # print "Tailpacket_size: %d Remaining Data:%d" % ( # chunk_size, remaining) packet += data[0:chunk_size - 1] data = data[chunk_size:] self.__packet_queues[priority].append( QueuedPacket(packet, True)) else: # dont think this works when i putfile it seems to fail # if it gets so big as to use huge chunk print "huge chunk total size:" + str(size) # 6 for reliable header + 6 for packet header MAX_CHUNK = MAX_PACKET - 12 while (len(data) > 0): chunk_size = min(MAX_CHUNK, len(data)) packet = struct.pack("<BBI", 0x00, 0x0a, size) packet += data[0:chunk_size - 1] data = data[chunk_size:] print "packet_size: %d Remaining Data:%d" % (chunk_size, len(data)) self.__packet_queues[priority].append( QueuedPacket(packet, True)) def __generate_next_outbound_packet(self): """Extract packets from queues and return a buffer containing the next outbound packet that is to be sent on the network, if a packet can be built. """ h = self.__packet_queues[PRIORITY_HIGH] n = self.__packet_queues[PRIORITY_NORMAL] if len(h) == 0 and len(n) == 0: return None # determine which packets can be clustered, if any clusterable_packets = 0 try: for packet_list in [h, n]: for outgoing_packet in packet_list: if outgoing_packet.total_packet_size() > 255: raise StopIteration() clusterable_packets += 1 except StopIteration: pass data = None if clusterable_packets > 1: data = struct.pack("<BB", 0x00, 0x0E) reliable_allowed = True for packet_list in [h, n]: i = 0 for p in packet_list[:]: # plus 1 is for the data length in cluster if p.total_packet_size() <= 255 and \ len(data) + p.total_packet_size() + 1 <= \ MAX_PACKET: if p.reliable: # send reliable packet, can be disallowed due to # ordering concerns if reliable_allowed: packet = struct.pack( "<BBBI", p.total_packet_size(), 0x00, 0x03, self.__next_outgoing_ack_id & 0xFFFFFFFF) + p.data # store off packet[1:] here so when its # retransmitted, it doesnt re-append the # 'size' field since only the data should be # resent self.__reliable_messages_in_transit[ self.__next_outgoing_ack_id] = \ [packet[1:], get_tick_count_hs()] self.__next_outgoing_ack_id += 1 data += packet packet_list[i] = None else: # send an unreliable packet data += struct.pack('<B', p.total_packet_size()) + p.data packet_list[i] = None elif p.reliable: # this packet wont fit and it is reliable, so # dont allow following reliable packets to be # sent, because these need to be sent in order reliable_allowed = False i += 1 # remove None entries # opt: should these be # self.__packet_queues[PRIORITY_Xxx][:] = ... ? self.__packet_queues[PRIORITY_HIGH] = [ x for x in h if x is not None ] self.__packet_queues[PRIORITY_NORMAL] = [ x for x in n if x is not None ] else: # a single, non-cluster packet is being sent if len(h): p = h.pop(0) else: p = n.pop(0) data = p.data if p.reliable: # send packet with the reliable header prepended data = struct.pack( "<BBI", 0x00, 0x03, self.__next_outgoing_ack_id & 0xFFFFFFFF) + data self.__reliable_messages_in_transit[ self.__next_outgoing_ack_id] = [data, get_tick_count_hs()] self.__next_outgoing_ack_id += 1 return data def flush_outbound_queues(self): """Flush outbound packet queues.""" while 1: if len(self.__packet_queues[PRIORITY_HIGH]) == 0 \ and len(self.__packet_queues[PRIORITY_NORMAL]) == 0: break # check to make sure outbound socket is writable rlist, wlist, xlist = select.select([], [self.__socket], [], 0) if len(wlist) == 0: break packet = self.__generate_next_outbound_packet() if packet: self.__send_raw_packet(packet) def __queue_ack_packet(self, ack_id): """Queue an ack packet.""" self.queue_packet(struct.pack("<BBI", 0x00, 0x04, ack_id & 0xFFFFFFFF)) def __queue_disconnect_packet(self): """Queue a disconnect packet.""" self.queue_packet(struct.pack("<BB", 0x00, 0x07)) def disconnect_from_server(self): """Disconnect from the server.""" self.__queue_disconnect_packet() self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) self.reconnect = False def should_reconnect(self): return self.reconnect def __handle_reliable_message(self, packet): """Handle an incoming reliable message.""" # this could be cleaner by adding to the incoming list, then # processing lists zero, core_type, ack_id = struct.unpack_from("<BBI", packet) self.__queue_ack_packet(ack_id) if ack_id == self.__next_incoming_ack_id: self.__next_incoming_ack_id += 1 self.__process_incoming_packet(packet[6:]) loop_again = True while loop_again: loop_again = False index = 0 for ack_packet_tuple in self.__incoming_reliable_packets: if ack_packet_tuple[0] == self.__next_incoming_ack_id: self.__incoming_reliable_packets.pop(index) self.__next_incoming_ack_id += 1 self.__process_incoming_packet(ack_packet_tuple[1]) loop_again = True break index += 1 elif ack_id > self.__next_incoming_ack_id: self.__incoming_reliable_packets.append((ack_id, packet[6:])) def __handle_chunk(self, packet): if self.__chunk_list is None: self.__chunk_list = [] # print "small chunk type: " + packet.encode('hex') self.__chunk_list.append(packet[2:]) def __handle_chunk_end(self, packet): if self.__chunk_list: self.__chunk_list.append(packet[2:]) self.__process_incoming_packet(''.join(self.__chunk_list)) self.__chunk_list = None # added by junky def __handle_huge_chunk(self, packet): type, type2, total_size = struct.unpack_from("<BBI", packet) if self.__huge_chunk_data is None: # new chunk self.__huge_chunk_data = packet[6:] print "huge chunk type: " + self.__huge_chunk_data.encode('hex') else: self.__huge_chunk_data += packet[6:] if len(self.__huge_chunk_data) == total_size: # packet complete self.__process_incoming_packet(self.__huge_chunk_data[:]) self.__huge_chunk_data = None def wait_for_event(self): """Wait for an event to occur. If no event is immediately available this call blocks. Must be called frequently for good performance.""" while self.__connected: self.__process_incoming_packets() self.flush_outbound_queues() # process tick event if necessary now = get_tick_count_hs() self.__event_tick_tick_accumulator += now - \ self.__last_wait_for_event_call_tick self.__last_wait_for_event_call_tick = now while self.__event_tick_tick_accumulator > EVENT_TICK_INTERVAL: self.__add_pending_event(CoreEvent(EVENT_TICK)) self.__event_tick_tick_accumulator -= EVENT_TICK_INTERVAL # create internal periodic core event if tick_diff(now, self.__last_core_periodic_event_tick) > \ EVENT_INTERNAL_CORE_PERIODIC_INTERVAL: self.__core_periodic_event() self.__last_core_periodic_event_tick = now # process pending events first if len(self.__event_list) > 0: # preprocess the event event = self.__event_list.pop(0) if event.type == EVENT_DISCONNECT: self.__connected = False return event # nothing left to do, wait on an event timeout = float(EVENT_TICK_INTERVAL - self.__event_tick_tick_accumulator) / 100 # if there are no packets to be sent, just wait for a read # otherwise wait for a read or the outbound socket to be writable if len(self.__packet_queues[PRIORITY_HIGH]) == 0 and \ len(self.__packet_queues[PRIORITY_NORMAL]) == 0: select.select([self.__socket], [], [], timeout) else: select.select([self.__socket], [self.__socket], [], timeout) def __process_incoming_packets(self): """Process all incoming packets.""" # process incoming packets, if any exist while True: packet = self.__read_raw_packet(0) if packet is None: break self.__process_incoming_packet(packet) def __core_periodic_event(self): """This is called every 100ms""" # requeue reliable messages that havent been acked now = get_tick_count_hs() for ack_id, packet_last_transmit_tick_list in \ self.__reliable_messages_in_transit.iteritems(): if tick_diff(now, packet_last_transmit_tick_list[1]) > \ RELIABLE_RETRANSMIT_INTERVAL: self.queue_packet(packet_last_transmit_tick_list[0], False, PRIORITY_HIGH) packet_last_transmit_tick_list[1] = now if tick_diff(now, self.__last_packet_received_tick) >= \ NO_PACKET_RECEIVED_TIMEOUT_INTERVAL: self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) def __handle_cluster_packet(self, packet): packet = packet[2:] while len(packet): data_len, = struct.unpack_from("<B", packet) self.__process_incoming_packet(packet[1:data_len + 1]) packet = packet[data_len + 1:] def __handle_disconnect_packet(self, packet): self.__add_pending_event(CoreEvent(EVENT_DISCONNECT)) def __handle_sync_request_packet(self, packet): self.__queue_sync_response() def __handle_sync_response_packet(self, packet): self.__last_sync_response_received_tick = get_tick_count_hs() def __handle_ack_packet(self, packet): zero, type, ack_id = struct.unpack_from("<BBI", packet) self.__reliable_messages_in_transit.pop(ack_id, None) def __process_incoming_packet(self, packet): # process the incoming packet, etc try: type, = struct.unpack_from("<B", packet) if type == 0x00: # core packet handlers type, = struct.unpack_from("<B", packet, 1) if self.__debug: self.__log(DEBUG, "Handling Core Type: 0x%02X" % type) handler = self.__packet_handlers.get(type, None) if handler: handler(packet) else: self.__log(INFO, "wtf corestack type %i not handled" % type) else: if self.__debug: self.__log(DEBUG, "Handling Game Type: 0x%02X" % type) event = CoreEvent(EVENT_GAME_PACKET_RECEIVED) event.packet = packet self.__add_pending_event(event) except (IndexError, struct.error): self.__log(CRITICAL, "Error in packet processing") self.__log(CRITICAL, "Packet data: " + packet.encode('hex')) formatted_lines = traceback.format_exc().splitlines() for l in formatted_lines: self.__log(DEBUG, l) def __read_raw_packet(self, timeout): """Read a raw packet on the network, optionally blocking on the socket.""" rlist, wlist, xlist = select.select([self.__socket], [], [], timeout) if len(rlist) == 0: time.sleep(0.01) return None # try: # if self.__timeout_interval != timeout: # self.__timeout_interval = timeout # self.__socket.settimeout(self.__timeout_interval) # packet = self.__socket.recv(MAX_PACKET) # except socket.timeout as e: # #self.logger.info("timeout") # return packet = rlist[0].recv(MAX_PACKET) self.__total_packets_received += 1 # decrypt the packet if self.__crypto: try: type, = struct.unpack_from("<B", packet) begin_offset = 1 if type == 0x00: begin_offset = 2 packet = packet[:begin_offset] + self.__crypto.decrypt_data( packet[begin_offset:]) except (IndexError, struct.error): packet = None if packet: if self.__debug: self.__log(DEBUG, "Read:" + packet.encode('hex')) self.__last_packet_received_tick = get_tick_count_hs() return packet def _queue_sync_request(self): self.queue_packet(struct.pack( "<BBIII", 0x00, 0x05, get_tick_count_hs(), self.__total_packets_sent & 0xFFFFFFFF, self.__total_packets_received & 0xFFFFFFFF), priority=PRIORITY_HIGH) def __queue_sync_response(self): self.queue_packet(struct.pack( "<BBII", 0x00, 0x06, get_tick_count_hs(), tick_diff(get_tick_count_hs(), self.__last_sync_response_received_tick)), priority=PRIORITY_HIGH) # def __handle_sync_response_packet(self, packet): # # packet contents are irrelevant # self.__last_sync_response_received_tick = get_tick_count_hs() def __queue_ack_message(self, ack_id): # not used anywhere? self.queue_packet(struct.pack("<BBI", 0x00, 0x03, ack_id & 0xFFFFFFFF), priority=PRIORITY_HIGH) def __send_raw_packet(self, packet): if self.__debug: self.__log(DEBUG, "Sent:" + packet.encode('hex')) if self.__crypto: begin_offset = 1 if ord(packet[0]) == 0x00: begin_offset = 2 packet = packet[:begin_offset] + self.__crypto.encrypt_data( packet[begin_offset:]) self.__socket.sendall(packet) self.__total_packets_sent += 1
def connect_to_server(self, server, port, newconn=1): """Connect to the server, otherwise raise an exception.""" self.__total_packets_sent = 0 self.__total_packets_received = 0 self.__next_outgoing_ack_id = 0 self.__next_incoming_ack_id = 0 self.__last_sync_response_received_tick = get_tick_count_hs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = get_tick_count_hs() self.__last_core_periodic_event_tick = get_tick_count_hs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) # changed for jython support doesnt seem to affect python self.__socket.setblocking(False) # self.__socket.settimeout(.01) self.__timeout_interval = 0.01 STATE_NONE = 0 STATE_CHALLENGE_RECEIVED = 1 STATE_CONNECTED = 2 client_key = (-random.randrange(1, sys.maxint)) server_challenge = 0 self.fc = FileChecksum() self.fc.generate_checksum_array(0) state = STATE_NONE timeouts = 0 while state != STATE_CONNECTED: if timeouts > 3: break # send queueing the packets directly is done here because # handshake packets are special if state == STATE_NONE: # send handshake initiation packet self.queue_packet(struct.pack( "<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queue_packet(struct.pack( "<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, get_tick_count_hs() )) # write sent packets to the network self.flush_outbound_queues() # read raw packet is used here because the packets # during handshake are a little bit special (such # as the 0x02 type containing key data) packet = self.__read_raw_packet(2) if packet is None: timeouts += 1 continue try: game_type, core_type = struct.unpack_from("<BB", packet) # default to bad state, unless we get an expected packet state = STATE_NONE if game_type == 0x00: if core_type == 0x05: # response to __sendClientKey() server_challenge, = struct.unpack_from("<i", packet, 2) state = STATE_CHALLENGE_RECEIVED elif core_type == 0x02: # success server_key, = struct.unpack_from("<i", packet, 2) self.__crypto = SubspaceEncryption( client_key, server_key) state = STATE_CONNECTED break except struct.error: pass if state != STATE_CONNECTED: self.__log(CRITICAL, "Unable to connect to server") self.__connected = False else: self.__connected = True
def connect_to_server(self, server, port, newconn=1): """Connect to the server, otherwise raise an exception.""" self.__total_packets_sent = 0 self.__total_packets_received = 0 self.__next_outgoing_ack_id = 0 self.__next_incoming_ack_id = 0 self.__last_sync_response_received_tick = get_tick_count_hs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = get_tick_count_hs() self.__last_core_periodic_event_tick = get_tick_count_hs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) # changed for jython support doesnt seem to affect python self.__socket.setblocking(False) # self.__socket.settimeout(.01) self.__timeout_interval = 0.01 STATE_NONE = 0 STATE_CHALLENGE_RECEIVED = 1 STATE_CONNECTED = 2 client_key = (-random.randrange(1, sys.maxint)) server_challenge = 0 self.fc = FileChecksum() self.fc.generate_checksum_array(0) state = STATE_NONE timeouts = 0 while state != STATE_CONNECTED: if timeouts > 3: break # send queueing the packets directly is done here because # handshake packets are special if state == STATE_NONE: # send handshake initiation packet self.queue_packet( struct.pack("<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queue_packet( struct.pack("<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, get_tick_count_hs())) # write sent packets to the network self.flush_outbound_queues() # read raw packet is used here because the packets # during handshake are a little bit special (such # as the 0x02 type containing key data) packet = self.__read_raw_packet(2) if packet is None: timeouts += 1 continue try: game_type, core_type = struct.unpack_from("<BB", packet) # default to bad state, unless we get an expected packet state = STATE_NONE if game_type == 0x00: if core_type == 0x05: # response to __sendClientKey() server_challenge, = struct.unpack_from("<i", packet, 2) state = STATE_CHALLENGE_RECEIVED elif core_type == 0x02: # success server_key, = struct.unpack_from("<i", packet, 2) self.__crypto = SubspaceEncryption( client_key, server_key) state = STATE_CONNECTED break except struct.error: pass if state != STATE_CONNECTED: self.__log(CRITICAL, "Unable to connect to server") self.__connected = False else: self.__connected = True