class CoreStack: """The core stack.""" def __init__(self, debug=False,logger=None): self.__debug = debug self.logger = logger self.__socket = None self.__crypto = None # initialized after servers key is received. once set, encryption is done self.__packet_queues = [[], []] # list containing QueuedPacket objects self.__reliable_messages_in_transit = { } # 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.__incoming_reliable_packets = [] # (ack_id, packet) reliable messages that were received out of order, packet does not include the reliable header self.__event_list = [] # the list of pending CoreEvent objects self.__last_packet_received_tick = GetTickCountHs() # the packet handler functions self.__packet_handlers = { 0x03 : self.__handleReliableMessage, 0x04 : self.__handleAckPacket, 0x05 : self.__handleSyncRequestPacket, 0x06 : self.__handleSyncResponsePacket, 0x07 : self.__handleDisconnectPacket, 0x08 : self.__handleChunk, 0x09 : self.__handleChunkEnd, 0x0A : self.__handleHugeChunk, 0x0D : self.__handleDisconnectPacket, #xxx we could parse the reason out of this 0x0E : self.__handleClusterPacket, } # 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 resetState(self):#for recop self.__socket = None self.__crypto = None # initialized after servers key is received. once set, encryption is done self.__packet_queues = [[], []] # list containing QueuedPacket objects self.__reliable_messages_in_transit = { } # 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.__incoming_reliable_packets = [] # (ack_id, packet) reliable messages that were received out of order, packet does not include the reliable header self.__event_list = [] # the list of pending CoreEvent objects def __log(self,level,message): if self.logger: self.logger.log(level,message) else: print (message) def __addPendingEvent(self, core_event): """Adds CoreEvent to the list for handling.""" self.__event_list.append(core_event) def connectToServer(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 = GetTickCountHs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = GetTickCountHs() self.__last_core_periodic_event_tick = GetTickCountHs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) self.__socket.setblocking(False) #changed for jython support doesnt seem to affect python #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.generateChecksumArray(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.queuePacket(struct.pack("<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queuePacket(struct.pack("<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, GetTickCountHs())) # write sent packets to the network self.flushOutboundQueues() # 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.__readRawPacket(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.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 queuePacket(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)) MAX_CHUNK = MAX_PACKET - 8# 6 for reliable header + 2 for packet header 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 offset =0 print "huge chunk total size:" + str(size) MAX_CHUNK = MAX_PACKET - 12# 6 for reliable header + 6 for packet header 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 __generateNextOutboundPacket(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.totalPacketSize() > 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[:]: if p.totalPacketSize() <= 255 and len(data) + p.totalPacketSize() + 1 <= MAX_PACKET: #plus 1 is for the data length in cluster if p.reliable: #send reliable packet, can be disallowed due to ordering concerns if reliable_allowed: packet = struct.pack("<BBBI", p.totalPacketSize(), 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:], GetTickCountHs()] self.__next_outgoing_ack_id += 1 data += packet packet_list[i] = None else: # send an unreliable packet data += struct.pack('<B', p.totalPacketSize()) + 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, GetTickCountHs()] self.__next_outgoing_ack_id += 1 return data def flushOutboundQueues(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.__generateNextOutboundPacket() if packet: self.__sendRawPacket(packet) def __queueAckPacket(self, ack_id): """Queue an ack packet.""" self.queuePacket(struct.pack("<BBI", 0x00, 0x04, ack_id & 0xFFFFFFFF)) def __queueDisconnectPacket(self): """Queue a disconnect packet.""" self.queuePacket(struct.pack("<BB", 0x00, 0x07)) def disconnectFromServer(self): """Disconnect from the server.""" self.__queueDisconnectPacket() self.__addPendingEvent(CoreEvent(EVENT_DISCONNECT)) self.reconnect = False def shouldReconnect(self): return self.reconnect def __handleReliableMessage(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.__queueAckPacket(ack_id) if ack_id == self.__next_incoming_ack_id: self.__next_incoming_ack_id += 1 self.__processIncomingPacket(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.__processIncomingPacket(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 __handleChunk(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 __handleChunkEnd(self, packet): if self.__chunk_list: self.__chunk_list.append(packet[2:]) self.__processIncomingPacket(''.join(self.__chunk_list)) self.__chunk_list = None #added by junky def __handleHugeChunk(self,packet): type,type2, total_size = struct.unpack_from("<BBI",packet) if(self.__huge_chunk_data == 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.__processIncomingPacket(self.__huge_chunk_data[:]) self.__huge_chunk_data = None def waitForEvent(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.__processIncomingPackets() self.flushOutboundQueues() # process tick event if necessary now = GetTickCountHs() 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.__addPendingEvent(CoreEvent(EVENT_TICK)) self.__event_tick_tick_accumulator -= EVENT_TICK_INTERVAL # create internal periodic core event if TickDiff(now, self.__last_core_periodic_event_tick) > EVENT_INTERNAL_CORE_PERIODIC_INTERVAL: self.__corePeriodicEvent() 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 __processIncomingPackets(self): """Process all incoming packets.""" # process incoming packets, if any exist while True: packet = self.__readRawPacket(0) if packet is None: break self.__processIncomingPacket(packet) def __corePeriodicEvent(self): """This is called every 100ms""" # requeue reliable messages that havent been acked now = GetTickCountHs() for ack_id, packet_last_transmit_tick_list in self.__reliable_messages_in_transit.iteritems(): if TickDiff(now, packet_last_transmit_tick_list[1]) > RELIABLE_RETRANSMIT_INTERVAL: self.queuePacket(packet_last_transmit_tick_list[0], False, PRIORITY_HIGH) packet_last_transmit_tick_list[1] = now if TickDiff(now, self.__last_packet_received_tick) >= NO_PACKET_RECEIVED_TIMEOUT_INTERVAL: self.__addPendingEvent(CoreEvent(EVENT_DISCONNECT)) def __handleClusterPacket(self, packet): packet = packet[2:] while len(packet): data_len, = struct.unpack_from("<B", packet) self.__processIncomingPacket(packet[1:data_len + 1]) packet = packet[data_len + 1:] def __handleDisconnectPacket(self, packet): self.__addPendingEvent(CoreEvent(EVENT_DISCONNECT)) def __handleSyncRequestPacket(self, packet): self.__queueSyncResponse() def __handleSyncResponsePacket(self, packet): self.__last_sync_response_received_tick = GetTickCountHs() def __handleAckPacket(self, packet): zero, type, ack_id = struct.unpack_from("<BBI", packet) self.__reliable_messages_in_transit.pop(ack_id, None) def __processIncomingPacket(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.__addPendingEvent(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 __readRawPacket(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.decryptData(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 = GetTickCountHs() return packet def _queueSyncRequest(self): self.queuePacket(struct.pack("<BBIII", 0x00, 0x05, GetTickCountHs(), self.__total_packets_sent & 0xFFFFFFFF, self.__total_packets_received & 0xFFFFFFFF), priority=PRIORITY_HIGH) def __queueSyncResponse(self): self.queuePacket(struct.pack("<BBII", 0x00, 0x06, GetTickCountHs(), TickDiff(GetTickCountHs(), self.__last_sync_response_received_tick)), priority=PRIORITY_HIGH) #def __handleSyncResponsePacket(self, packet): # # packet contents are irrelevant # self.__last_sync_response_received_tick = GetTickCountHs() def __queueAckMessage(self, ack_id): self.queuePacket(struct.pack("<BBI", 0x00, 0x03, ack_id & 0xFFFFFFFF), priority=PRIORITY_HIGH) def __sendRawPacket(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.encryptData(packet[begin_offset:]) self.__socket.sendall(packet) self.__total_packets_sent += 1
def connectToServer(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 = GetTickCountHs() self.__event_tick_tick_accumulator = 0 self.__last_wait_for_event_call_tick = GetTickCountHs() self.__last_core_periodic_event_tick = GetTickCountHs() self.__server = str(server) self.__port = int(port) self.__socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.__socket.connect((server, port)) self.__socket.setblocking(False) #changed for jython support doesnt seem to affect python #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.generateChecksumArray(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.queuePacket(struct.pack("<BBIH", 0x00, 0x01, client_key & 0xFFFFFFFF, 0x0001)) elif state == STATE_CHALLENGE_RECEIVED: # respond with server challenge self.queuePacket(struct.pack("<BBII", 0x00, 0x06, server_challenge & 0xFFFFFFFF, GetTickCountHs())) # write sent packets to the network self.flushOutboundQueues() # 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.__readRawPacket(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.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