Example #1
0
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
Example #2
0
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
Example #3
0
    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
Example #4
0
    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