class BTCPClientSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self.window = window
        self.timeout = timeout
        self.termination_count = 5
        self.thread_executor = ThreadPoolExecutor(max_workers=window)
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)
        self.state = State.CLOSED

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        segment = btcp.packet.unpack_from_socket(segment)
        segment_type = segment.packet_type()
        if (segment_type == "SYN-ACK"):
            self.state = State.SYN_ACK_RECVD
            self.thread_executor.submit(self.handshake_ack_thread, segment)
        elif segment_type == "FIN_ACK":
            self.state = State.FIN_ACK_RECVD
        else:
            pass

    # Initiate a three-way handshake with the server.
    def connect(self):
        self.thread_executor.submit(self.con_establish_thread)
        self.state = State.SYN_SEND

    # Send data originating from the application in a reliable way to the server
    def send(self, data):
        pass

    # Perform a handshake to terminate a connection
    def disconnect(self):
        pass

    # Clean up any state
    def close(self):
        self.thread_executor.shutdown(wait=True)
        self._lossy_layer.destroy()
        print("CLIENT CLOSED")

    # Initiate termination of connection with server
    def close_client(self):
        self.thread_executor.submit(self.con_close_thread)
        self.close()

    # Send the response to the server's ACK of the handshake.
    def handshake_ack_thread(self, segment):
        seq_nr = segment.get_ack_nr() + 1
        ack_nr = segment.get_seq_nr() + 1
        segment.up_seq_nr(
            seq_nr - (ack_nr - 1)
        )  # remove old seq_nr (which is now ack_nr) and replace by new seq_nr
        segment.up_ack_nr(
            ack_nr - seq_nr
        )  # remove als ack_nr (which is now seq_nr) and replace by new ack_nr
        segment.set_flags(True, False, False)  # set ACK flag
        self._lossy_layer.send_segment(segment.pack())

    # Runnable function to establish a connection with the server
    def con_establish_thread(self):
        syn_nr = randint(0, 65535)  # random 16-bit integer
        segment = TCPpacket(syn_nr)
        segment.set_flags(False, True, False)  # set SYN flag
        send_segment = segment.pack()
        self._lossy_layer.send_segment(send_segment)
        while True:
            time.sleep(self.timeout / 1000)
            if self.state != State.SYN_ACK_RECVD:
                self._lossy_layer.send_segment(send_segment)
            else:
                self.state = State.HNDSH_COMP
                break

    # Runnable function to close the connection with the server
    def con_close_thread(self):
        segment = TCPpacket()
        segment.set_flags(False, False, True)
        send_segment = segment.pack()
        self._lossy_layer.send_segment(send_segment)
        while True:
            time.sleep(self.timeout / 1000)
            if self.state != State.FIN_ACK_RECVD and self.termination_count > 0:
                self._lossy_layer.send_segment(send_segment)
                self.termination_count -= 1
            else:
                self.state = State.CLOSED
                break
Exemple #2
0
class BTCPClientSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)
        self.connloop = asyncio.get_event_loop()
        self.tries = 1

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        rsegment = segment[0]
        s = bTCPSegment()
        s.decode(segment[0])

        if self.status == 1:
            # Step 2 of handshake
            self.finish_connect(s)
            self._rwindow = s.window
            return

        if SegmentType.ACK in s.flags and SegmentType.FIN in s.flags:
            self.status = 0
            try:
                self._lossy_layer.destroy()
            except RuntimeError:
                pass
        else:
            self.process_message(s, rsegment)

    # Perform a three-way handshake to establish a connection
    def connect(self, first=True):
        if self.status > 1 and first:
            raise AttributeError(
                "Attempt to establish connection when connection is already (being) established."
            )

        self._seqnum = int.from_bytes(urandom(2), byteorder)

        s = bTCPSegment()
        s = s.Factory() \
            .setFlag(SegmentType.SYN) \
            .setWindow(self._window) \
            .setSequenceNumber(self._seqnum)
        s = s.make()

        self._lossy_layer.send_segment(s)
        self.status = 1

        t = Timer(self._timeout, self.timeout, [self.connect])
        t.start()

    def timeout(self, callback):
        if self.status == 1 or self.status == 4:
            if self.tries < MAX_TRIES:
                self.tries += 1
                callback(False)
            else:
                self.close()
                raise TimeoutError("Connection to server timed out after " +
                                   str(self.tries) + " tries")

    def finish_connect(self, server_segment):
        if self.status > 1:
            raise AttributeError(
                "Attempt to establish connection when connection is already (being) established."
            )

        self._seqnum += 1
        self._acknum = server_segment.seqnumber + 1

        s = bTCPSegment()
        s = s.Factory() \
            .setFlag(SegmentType.ACK) \
            .setSequenceNumber(self._seqnum) \
            .setAcknowledgementNumber(self._acknum) \
            .setWindow(self._window)

        self._lossy_layer.send_segment(s.make())
        self.status = 3

    # Perform a handshake to terminate a connection
    def disconnect(self):
        self.sendFin()
        self.tries = 1
        self.status = 4

        t = Timer(self._timeout, self.timeout, [self.disconnect])
        t.start()

    # Sends a FIN package
    def sendFin(self):
        segment = bTCPSegment()
        segment = segment.Factory() \
            .setFlag(SegmentType.FIN) \
            .make()

        self._lossy_layer.send_segment(segment)
class BTCPServerSocket(BTCPSocket):
    data_buffer = b''

    receive_window = []

    seqnum_server_packet = 0
    seqnum_wanted = 0
    last_ack = 0
    timeout = 0
    window = 0

    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self.window = window
        self.timeout = timeout
        self._lossy_layer = LossyLayer(self, SERVER_IP, SERVER_PORT, CLIENT_IP,
                                       CLIENT_PORT)

    # Called by the lossy layer from another thread whenever a segment arrives
    def lossy_layer_input(self, segment):
        packet = segment[0]

        # Add the incoming packet to the back of the queue if it is approved
        # using the checksum
        if self._verify_cksum(packet):
            self.receive_window.append(packet)
        else:
            print("Checksum failed!")
        pass

    # Verify the checksum of packet
    def _verify_cksum(self, packet):
        received_chksum = struct.unpack('!H', packet[8:10])[0]
        if received_chksum == 0:
            return True

        buffer = array.array('B', packet)
        struct.pack_into('!H', buffer, 8, 0)
        computed_chksum = self.in_cksum(bytes(buffer))

        return received_chksum == computed_chksum

    # Wait for the client to initiate a three-way handshake and perform it
    def accept(self, cur_segment):
        handshake_complete = False
        x, flags = struct.unpack('!H2xB', cur_segment[0:5])

        # Create and send SYN + ACK
        y = random.randint(0, 65535)
        packet = struct.pack('!HH2B1012x', y, x + 1, SYN + ACK, self.window)
        self._lossy_layer.send_segment(packet)

        # Wait for ACK
        start_time = time.time()
        while time.time(
        ) - start_time < self.timeout and not handshake_complete:
            if self.receive_window:
                cur_segment = self._next_packet()
                inc_x, inc_y, flags = struct.unpack('!HHB', cur_segment[:5])

                # Complete three-way handshake
                if x + 1 == inc_x and y + 1 == inc_y and flags == ACK:
                    handshake_complete = True

        pass

    # Parse the first in line packet and handle accordingly, either:
    #   Respond to three-way handshake initiation of client
    #   Respond to close initiation of client
    #   send data in payload to the application layer
    def recv(self):
        payload = b''
        if self.receive_window:
            cur_segment = self._next_packet()
            flags = struct.unpack('!B', cur_segment[4:5])[0]
            # Respond to three-way handshake initiation of client
            if flags == SYN:
                self.accept(cur_segment)
            # Respond to close initiation of client
            elif flags == FIN:
                segment = struct.pack('!4xB1013x', ACK + FIN)
                self._lossy_layer.send_segment(segment)
                self.close()
                return False
            # Send data in payload to the application layer
            elif flags == NO_FLAGS:
                # Unpack received package
                seq_num, data_length, data = struct.unpack(
                    '!H4xH2x1008s', cur_segment)

                # Check ACK number and store data
                #   Respond with the correct Acknowledgement number,
                #   following the 'Go-Back-N' protocol
                if self.seqnum_wanted == 0:
                    if seq_num == 0:
                        packet = struct.pack('!HHB1013x',
                                             self.seqnum_server_packet,
                                             seq_num, ACK)
                        self._lossy_layer.send_segment(packet)
                        self.seqnum_server_packet += 1
                        self.seqnum_wanted += 1
                        payload = data[:data_length]
                else:
                    if seq_num == self.seqnum_wanted:
                        ack_num = seq_num
                        self.last_ack = seq_num
                        self.seqnum_wanted += 1
                        payload = data[:data_length]
                    else:
                        ack_num = self.last_ack

                    packet = struct.pack('!HHB1013x',
                                         self.seqnum_server_packet, ack_num,
                                         ACK)
                    self._lossy_layer.send_segment(packet)
                    self.seqnum_server_packet += 1
            else:
                print("Error: can't resolve packet")

        # Store all received data in data_buffer
        self.data_buffer += payload
        return True

    # Returns the next packet in the current window, using the FIFO principle
    def _next_packet(self):
        return self.receive_window.pop(0)

    # Returns the data_buffer
    def received_data(self):
        return self.data_buffer

    # Clean up any state
    def close(self):
        self._lossy_layer.destroy()
class BTCPClientSocket(BTCPSocket):
    # window_size is the size of receive_window of the server, timeout is the timeout time, receive_window contains
    # packets for the client.
    window_size = 0
    timeout = 0
    receive_window = []

    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self.timeout = TIMEOUT_TIME
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        packet = segment[0]
        self.receive_window.append(packet)

    # Perform a three-way handshake to establish a connection with the server
    # Returns if the establishment was successful
    def connect(self):
        handshake_complete = False
        current_tries = 0

        # Try to perform handshake within number of tries
        while not handshake_complete and current_tries < TRIES:
            # Step 1 of three-way handshake
            # Send packet with x and SYN flag
            x = random.randint(0, 65535)
            packet = struct.pack('!H2xB1013x', x, SYN)
            self._lossy_layer.send_segment(packet)
            current_tries += 1

            # Step 2 of three-way handshake
            start_time = time.time()
            while time.time(
            ) - start_time < self.timeout and not handshake_complete:
                if self.receive_window:
                    # Receive packet with y, x+1 and SYN+ACK flags
                    cur_segment = self.next_packet()
                    y, x_inc, flags, self.window_size = struct.unpack(
                        '!HHBB', cur_segment[:6])

                    # Step 3 of the three-way handshake
                    if (flags == SYN + ACK) and (x_inc == x + 1):
                        # Send packet with x+1, y+1 and ACK flag, complete handshake
                        packet = struct.pack('!HHB1013x', x_inc, y + 1, ACK)
                        self._lossy_layer.send_segment(packet)
                        handshake_complete = True

        return handshake_complete

    # Send data originating from the application in a reliable way to the server using Go-Back-N
    def send(self, input_data):
        # remaining_ack is the number of packets that still need to be acknowledged
        remaining_acks = self.window_size
        finished = False

        # Convert input_data to string, then fill packets with packets meant for the server. remaining_data contains
        # the data not in packets, seq_num the sequence number of the next to be send packet
        data = bytes(input_data, 'utf-8')
        packets, remaining_data, seq_num = self.fill_window(data)

        # While there is still data to be send, and packets to be acknowledged, execute
        while not finished:
            # Send packets that are currently in packets
            for i in range(len(packets)):
                self._lossy_layer.send_segment(packets[i])

            # Start timer and check if packets arrive
            current_time = time.time()
            while time.time() - current_time < self.timeout and not finished:
                # If the client received a packet
                if self.receive_window:
                    # Assign head of receive_window to cur_segment, unpack it
                    cur_segment = self.next_packet()
                    ack_num, flags = struct.unpack('!2xHB', cur_segment[:5])
                    packet_num = self._get_seqnum(packets[0])

                    # If packet contained an ACK and the corresponding acknowledgement number is larger or equal
                    # to the acknowledgement number that is to be acknowledged, proceed
                    if flags == ACK:
                        if ack_num >= packet_num:
                            # Remove acknowledged packet
                            packets.pop(0)

                            # Timer reset
                            current_time = time.time()

                            # Decrease the number of remaining packets to be acknowledged
                            remaining_acks -= 1

                            # Check if there is any data left to send
                            if remaining_data:
                                # Construct a new packet with sequence number of the last acknowledged packet plus
                                # the receive window size, append it to packets, then send that new packet
                                seq_num = packet_num + self.window_size
                                new_packet = self._construct_packet(
                                    seq_num, remaining_data[:PAYLOAD_SIZE])
                                packets.append(new_packet)
                                self._lossy_layer.send_segment(new_packet)

                                # Decrease remaining data with the size of a data segment, increment the packets that
                                # need to be acknowledged
                                remaining_data = remaining_data[PAYLOAD_SIZE:]
                                remaining_acks += 1

                            # If there is no data left to be send, and all sent packets are acknowledged then
                            # the client is finished with sending
                            elif not remaining_data and not remaining_acks:
                                finished = True

        pass

    # Construct an array of packets with the window size of the server
    # Returns a list of size window_size containing packets, data has not been put in packets yet
    # and the next sequence number
    def fill_window(self, data):
        packets = []
        remaining = data
        seq_num = 0

        # Fill packets with packets with the corresponding window size
        for i in range(self.window_size):
            seq_num = i
            data_segment = remaining[:PAYLOAD_SIZE]
            remaining = remaining[PAYLOAD_SIZE:]
            # If data_segment is not empty, append a new packet with sequence number seq_num
            # and data_segment to packets
            if data_segment:
                packets.append(self._construct_packet(seq_num, data_segment))

        return packets, remaining, seq_num + 1

    # Construct a single packet with sequence number seq_num containing data
    # Returns a single packet with sequence number seq_num containing data
    def _construct_packet(self, seq_num, data):
        data_length = len(data)

        # Add padding to data if necessary
        for i in range(PAYLOAD_SIZE - data_length):
            data += PADDING

        # Construct packet with sequence number seq_num, data and checksum 0, then compute the
        # checksum over that packet
        pckt_no_chksum = struct.pack('!H4xHH1008s', seq_num, data_length, 0,
                                     data)
        packet = struct.pack('!H4xHH1008s', seq_num, data_length,
                             self.in_cksum(pckt_no_chksum), data)

        return packet

    # Return the sequence number of a packet
    def _get_seqnum(self, packet):
        return int.from_bytes(packet[:2], "big")

    # Perform a handshake to terminate a connection
    def disconnect(self):
        disconnected = False
        current_tries = 0

        # While the disconnection is not completed and not exceeded the maximum amount of tries
        while not disconnected and current_tries < TRIES:
            # Step 1 of handshake, send FIN packet
            flags = FIN
            packet = struct.pack('!4xB1013x', flags)
            self._lossy_layer.send_segment(packet)

            # Step 2/3 of handshake, receive ACK+FIN then close connection
            start_time = time.time()
            while time.time() - start_time < TIMEOUT_TIME and not disconnected:
                # If there is a packet in the receive window, unpack and check for ACK+FIN flags, if there are
                # close the connection
                if self.receive_window:
                    cur_segment = self.next_packet()
                    flags = struct.unpack('!B', cur_segment[4:5])[0]
                    if flags == ACK + FIN:
                        disconnected = True
                        self.close()
        pass

    # Return the head packet in the receive window
    def next_packet(self):
        return self.receive_window.pop(0)

    # Clean up any state
    def close(self):
        self._lossy_layer.destroy()
Exemple #5
0
class BTCPClientSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)
        self._btcpsocket = BTCPSocket
        self._HandshakeSuccessful = False
        self._Disconnected = False
        self._timeout = timeout
        self._First_Packet_Sequence_Number = 0
        self._PacketList = []
        self._AckPacket = None
        self._SequenceNumberList = []
        self._Packetloss = 0
        self._packets1 = []
        self._retryCounter = 10

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        packet_bytes, address = segment
        self._PacketList.append(packet_bytes)

    # Perform a three-way handshake to establish a connection
    def connect(self):
        print("Trying handshake (client-sided)")
        while self._retryCounter >= 0 and not self._HandshakeSuccessful:
            self._PacketList = []
            header = Header(
                random.getrandbits(15) & 0xffff, 0, Flags(1, 0, 0),
                self._window, 0, 0
            )  #We use 15 bits instead of 16 because there are cases that random.getrandbits() returns a large integer that will overflow the 16 bit length when we add sequence numbers to it and eventually results in an error (struct.error: ushort format requires 0 <= number <= 0xffff)
            Syn_packet = Packet(header, "")
            Syn_packet_bytes = Syn_packet.pack_packet()
            self._lossy_layer.send_segment(Syn_packet_bytes)
            if self.wait_for_packet():
                Syn_Ack_Packet_bytes = self._PacketList[0]
                Syn_Ack_Packet = Packet.unpack_packet(Syn_Ack_Packet_bytes)
                self._First_Packet_Sequence_Number = Syn_Ack_Packet.getHeader(
                ).getAckNumber()
                Ack_number = Syn_Ack_Packet.getHeader().getSynNumber() + 1
                if self._btcpsocket.CheckChecksum(Syn_Ack_Packet_bytes):
                    Ack_header = Header(0, Ack_number, Flags(0, 1, 0),
                                        self._window, 0, 0)
                    Ack_packet = Packet(Ack_header, "")
                    self._AckPacket = Ack_packet
                    Ack_packetBytes = Ack_packet.pack_packet()
                    self._lossy_layer.send_segment(Ack_packetBytes)
                    self._HandshakeSuccessful = True
                    print("Client handshake successful")
                    self._PacketList = []
                else:
                    print(
                        "Retrying handshake, the checksum of the syn-ack packet is invalid"
                    )
                    self._retryCounter -= 1

            else:
                print("Retrying handshake, packet timeout")
                self._retryCounter -= 1
        if self._retryCounter < 0:
            print("Number of tries exceed, quitting connection...")

    def wait_for_packet(self):
        timeNow = time.time()
        while self._PacketList == [] and time.time(
        ) < timeNow + self._timeout * 0.001:
            time.sleep(0.001)
        if not self._PacketList:
            return False
        else:
            return True

    # Send data originating from the application in a reliable way to the server
    def send(self, data):
        packets = []
        content = open(data).read()
        sequence_number_updated = self._First_Packet_Sequence_Number

        # Making packet bytes from the input file
        while len(content) > 0:
            if len(content) >= 1008:
                header = Header(sequence_number_updated, 0, Flags(0, 0, 0),
                                self._window, 1008, 0)
                packet = Packet(header, content[:1008])
                packet_bytes = packet.pack_packet()
                content = content[1008:]
                self._SequenceNumberList.append(sequence_number_updated)
                sequence_number_updated += 1008
                packets.append(packet_bytes)
            else:
                header = Header(sequence_number_updated, 0, Flags(0, 0, 0),
                                self._window, len(content), 0)
                packet = Packet(header, content)
                packet_bytes = packet.pack_packet()
                packets.append(packet_bytes)
                self._SequenceNumberList.append(sequence_number_updated)
                sequence_number_updated += len(content)
                content = ""

        # Sending the packets to the client
        packet_length = len(packets)
        i = 0
        k = self._window
        while packet_length > self._window:
            packet_length = packet_length - self._window
            for j in range(i, k):
                self.ResubmitPacket(packets[j])
            i += self._window
            k += self._window
        for m in range(i, len(packets)):
            self.ResubmitPacket(packets[m])

    # We implemented a stop-and-wait protocol, every packet we send to the server we wait until we receive the right ack
    # packet from the server, else we resend the packet
    def ResubmitPacket(self, packetbytes):
        self._PacketList = []
        self._lossy_layer.send_segment(packetbytes)
        while not self.wait_for_packet():
            self._lossy_layer.send_segment(packetbytes)
        packet = Packet.unpack_packet(packetbytes)
        while not self.CheckIfAckPacketAndCorrectNumber(
                packet.getHeader().getSynNumber() +
                packet.getHeader().getDatalength()):
            if Packet.unpack_packet(self._PacketList[0]).getHeader().getFlags(
            ).flag_to_int() == 6:
                self._lossy_layer.send_segment(self._AckPacket.pack_packet())
                self._PacketList = []
                while not self.wait_for_packet():
                    self._lossy_layer.send_segment(
                        self._AckPacket.pack_packet())
            else:
                self._lossy_layer.send_segment(packetbytes)
                self._PacketList = []
                while not self.wait_for_packet():
                    self._lossy_layer.send_segment(packetbytes)

    # Check if the ack packet we receive from the server coincides with the data packet we just sent
    def CheckIfAckPacketAndCorrectNumber(self, SynNumber):
        if Packet.unpack_packet(self._PacketList[0]).getHeader().getFlags(
        ).flag_to_int() == 2 and Packet.unpack_packet(
                self._PacketList[0]).getHeader().getAckNumber() == SynNumber:
            return True

        for i in range(0, len(self._SequenceNumberList)):
            if Packet.unpack_packet(self._PacketList[0]).getHeader(
            ).getAckNumber() == self._SequenceNumberList[i]:
                self._Packetloss = i
        return False

    # Perform a handshake to terminate a connection
    def disconnect(self):
        print("Trying to disconnect (client-sided):")
        self._retryCounter = 10
        while self._retryCounter >= 0 and not self._Disconnected:
            self._PacketList = []
            header = Header(
                random.getrandbits(15) & 0xffff, 0, Flags(0, 0, 1),
                self._window, 0, 0
            )  #We use 15 bits instead of 16 because there are cases that random.getrandbits() returns a large integer that will overflow the 16 bit length when we add sequence numbers to it and eventually results in an error (struct.error: ushort format requires 0 <= number <= 0xffff)
            Fin_packet = Packet(header, "fin")
            Fin_packet_bytes = Fin_packet.pack_packet()
            self._lossy_layer.send_segment(Fin_packet_bytes)
            if self.wait_for_packet():
                if Packet.unpack_packet(self._PacketList[0]).getHeader(
                ).getFlags().flag_to_int() == 3:
                    Fin_Ack_Packet_bytes = self._PacketList[0]
                    self._PacketList.pop(0)
                    Fin_Ack_Packet = Packet.unpack_packet(Fin_Ack_Packet_bytes)
                    Ack_number = Fin_Ack_Packet.getHeader().getSynNumber() + 1
                    if self._btcpsocket.CheckChecksum(Fin_Ack_Packet_bytes):
                        Ack_header = Header(0, Ack_number, Flags(0, 1, 0),
                                            self._window, 0, 0)
                        Ack_packet = Packet(Ack_header, "ack")
                        Ack_packet_bytes = Ack_packet.pack_packet()
                        self._lossy_layer.send_segment(Ack_packet_bytes)
                        self._Disconnected = True
                        print("Client disconnection successful")
                    else:
                        print(
                            "Retrying disconnection, the checksum of the fin-ack packet is invalid"
                        )
                        self._retryCounter -= 1
                else:
                    print(
                        "Retrying handshake, the received packet is not a fin-ack packet"
                    )
                    self._retryCounter -= 1
            else:
                print("Retrying handshake, packet timeout")
                self._retryCounter -= 1
        if self._retryCounter < 0:
            print("Number of tries exceeded, going to disconnect")
            self._Disconnected = True

    # Clean up any state and close the socket
    def close(self):
        self._lossy_layer.destroy()
        pass
class BTCPClientSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP, SERVER_PORT)

        # Boolean variable that determines whether the server has a connection to the cient
        self._connected_to_server = False

        # Global variables that keep track of the sequence and the acknowledgement number that are send during
        # the three-way handshake
        self._x_value = 0
        self._y_value = 0

        # Variable that keeps track of the window size on the server side
        self._window_size_server = 0

        # Selective repeater
        self._selective_repeater = SelectiveRepeaterSender(self._lossy_layer, self._window, self._timeout)

    # Called by the lossy layer from another thread whenever a segment arrives. 
    def lossy_layer_input(self, rec_data):
        (header, packet) = rec_data
        seg_info = unpack_segment(header)
        (seq_number, ack_number, (ack, syn, fin), window, data_length, checksum, data) = seg_info
        if not syn and ack and not fin:
            if self._selective_repeater is not None:
                self._selective_repeater.ReceiveAckPacket(seg_info)
            else:
                print("Selective repeat protocol not initiated, but still receiving ACK")
        if syn and ack and not fin:
            self.acknowledge_server(seg_info)
        if not syn and ack and fin:
            # Terminate connection between client and server
            self._connected_to_server = False
            print("disconnected to server")

    # Perform a three-way handshake to establish a connection
    def connect(self):
        # Variable that keeps track of the moment when the first step of the three-way handshake was initiated.
        # Used to determine whether a connection timeout has occurred.
        connect_start = time.time()

        # Variable that keeps track of the number of attempts to connect to the server.
        # Used to determine whether the max number of connection attempts was exceeded.
        connect_attempts = 1

        self.synchonize_server()
        print("connecting...")

        while not self._connected_to_server:
            if ((time.time() - connect_start) * 1000) >= self._timeout:
                # Connection Timeout
                if connect_attempts >= MAX_NUMBER_OF_CONNECTION_TRIES:
                    # Max number of connection tries was exceeded
                    print("Connection attempts failed: Max number of connection tries ({}) exceeded.".format(MAX_NUMBER_OF_CONNECTION_TRIES))
                    break
                else:
                    #Try to connect again
                    print("Connection attempt failed: Connection timeout, retrying...")
                    connect_attempts += 1
                    connect_start = time.time()
                    self.synchonize_server()


    # Step 3 of the three-way handshake to establish connection
    def synchonize_server(self):
        # Step 1 of three way handshake
        self._x_value = random.randint(0, MAX_VALUE_16_BIT_INTEGER + 1)
        ack_syn_fin = flags_to_binary(False, True, False)
        segment = Segment(self._x_value, 0, ack_syn_fin, 0, 0, 0, None)
        data = segment.create_segment()
        self._lossy_layer.send_segment(data)

    # Step 3 of the three-way handshake to establish connection
    def acknowledge_server(self, seg_info):
        # Step 3 of three way handshake
        (seq_number, ack_number, (ack, syn, fin), window, data_length, checksum, data) = seg_info
        if ack_number != self._x_value+1:
            print("acknowledgement number incorrect:", ack_number, self._x_value+1)
        else:
            self._y_value = seq_number
            self._window_size_server = window
            ack_syn_fin = flags_to_binary(True, False, False)
            segment = Segment(self._x_value+1, self._y_value+1, ack_syn_fin, 0, 0, 0, None)
            data = segment.create_segment()
            self._lossy_layer.send_segment(data)
            print("connected to server.")
            self._connected_to_server = True


    # Send data originating from the application in a reliable way to the server
    def send(self, data):
        self._selective_repeater.StartSending(data)

    # Perform a handshake to terminate a connection
    def disconnect(self):
        # Variable that keeps track of the moment when the first step of the termination handshake was initiated.
        # Used to determine whether a termination timeout has occurred.
        terminate_start = time.time()

        # Variable that keeps track of the number of attempts to terminate the connection to the server.
        # Used to determine whether the max number of termination attempts was exceeded.
        terminate_attempts = 1

        self.finish_server()
        print("disconnecting...")

        while self._connected_to_server:
            if ((time.time() - terminate_start) * 1000) >= self._timeout:
                # Connection Timeout
                if terminate_attempts >= MAX_NUMBER_OF_TERMINATION_TRIES:
                    # Max number of connection tries was exceeded
                    print("Termination attempts failed: Max number of termination tries ({}) exceeded.".format(
                        MAX_NUMBER_OF_TERMINATION_TRIES))
                    self._connected_to_server = False
                    break
                else:
                    # Try to connect again
                    print("Termination attempt failed: Termination timeout, retrying...")
                    terminate_attempts += 1
                    terminate_start = time.time()
                    self.finish_server()

    # First step of termination handshake
    def finish_server(self):
        ack_syn_fin = flags_to_binary(False, False, True)
        segment = Segment(0, 0, ack_syn_fin, 0, 0, 0, None)
        data = segment.create_segment()
        self._lossy_layer.send_segment(data)

    def clean(self):
        self._connected_to_server = False
        self._x_value = 0
        self._y_value = 0
        self._window_size_server = 0
        self._selective_repeater = SelectiveRepeaterSender(self._lossy_layer, self._window, self._timeout)

    # Clean up any state
    def close(self):
        self._lossy_layer.destroy()
        self._connected_to_server = False
        self._x_value = 0
        self._y_value = 0
        self._window_size_server = 0
class BTCPServerSocket(BTCPSocket):
    def __init__(self, window, timeout):
        self._btcpsocket = BTCPSocket
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, SERVER_IP, SERVER_PORT, CLIENT_IP,
                                       CLIENT_PORT)
        self._HandshakeSuccessful = False
        self._Disconnected = False
        self._PacketList = []
        self._OutputList = []
        self._PacketCounter = 0
        self._AckNumber = 0

    # Called by the lossy layer from another thread whenever a segment arrives
    def lossy_layer_input(self, segment):
        packet_bytes, address = segment
        self._PacketList.append(packet_bytes)
        self._ReceivedPacket = packet_bytes
        self._PacketCounter += 1
        pass

    # Wait for the client to initiate a three-way handshake
    def accept(self):
        print("Trying handshake (server-sided)")
        while not self._HandshakeSuccessful:
            self._PacketList = []
            self.wait_for_packet()  # Wait for the Syn packet from the client
            Syn_packet_bytes = self._PacketList[0]
            Syn_packet = Packet.unpack_packet(Syn_packet_bytes)
            self._PacketList.pop(0)
            if Syn_packet.getHeader().getFlags().flag_to_int(
            ) == 4 and self._btcpsocket.CheckChecksum(Syn_packet_bytes):
                # If the received packet from the client is correct and is a syn packet, create a syn-ack packet and
                # send it to the client
                flags = Flags(1, 1, 0)
                ack_number = Syn_packet.getHeader().getSynNumber() + 1
                header = Header(
                    random.getrandbits(15) & 0xffff, ack_number, flags,
                    self._window, 0, 0)
                Syn_Ack_packet = Packet(header, "")
                Syn_Ack_packet_bytes = Syn_Ack_packet.pack_packet()
                self._AckNumber = ack_number
                self._lossy_layer.send_segment(Syn_Ack_packet_bytes)
                self.wait_for_packet(
                )  # Wait for the Ack packet from the client
                Ack_packet_bytes = self._PacketList[0]
                Ack_packet = Packet.unpack_packet(Ack_packet_bytes)
                self._PacketList.pop(0)
                if Ack_packet.getHeader().getFlags().flag_to_int() == 2:
                    if self._btcpsocket.CheckChecksum(Ack_packet_bytes):
                        self._HandshakeSuccessful = True
                        print("Server handshake successful.")
                    else:
                        print(
                            "Ack packet checksum incorrect, retrying handshake..."
                        )
                else:
                    print(
                        "Received packet is not an Ack packet, resending the syn-ack packet..."
                    )
                    while not self._HandshakeSuccessful:
                        self._lossy_layer.send_segment(Syn_Ack_packet_bytes)
                        self.wait_for_packet()
                        packet = Packet.unpack_packet(self._PacketList[0])
                        self._PacketList.pop(0)
                        if packet.getHeader().getFlags().flag_to_int() == 2:
                            if self._btcpsocket.CheckChecksum(
                                    packet.pack_packet()):
                                self._HandshakeSuccessful = True
                                print("Server handshake successful")
            else:
                print(
                    "Retrying handshake, the checksum of the packet is invalid or the packet is not an ack packet"
                )

    # Wait until a packet is received from the client
    def wait_for_packet(self):
        while not self._PacketList:
            time.sleep(0.001)

    # Send any incoming data to the application layer
    def recv(self):
        self.wait_for_packet()
        while not Packet.unpack_packet(
                self._PacketList[0]).getHeader().getFlags().flag_to_int() == 1:
            packet = Packet.unpack_packet(self._PacketList[0])
            if packet.getHeader().getSynNumber() == self._AckNumber:
                self._AckNumber += packet.getHeader().getDatalength()
                header = Header(0, self._AckNumber, Flags(0, 1, 0),
                                self._window, 0, 0)
                AckPacket = Packet(header, "")
                AckPacket_bytes = AckPacket.pack_packet()
                self._lossy_layer.send_segment(AckPacket_bytes)
                self._OutputList.append(self._PacketList[0])
                self._PacketList.pop(0)
                self.wait_for_packet()
            else:
                header = Header(0, self._AckNumber, Flags(0, 1, 0),
                                self._window, 0, 0)
                AckPacket = Packet(header, "")
                AckPacket_bytes = AckPacket.pack_packet()
                self._lossy_layer.send_segment(AckPacket_bytes)
                self._PacketList.pop(0)
                self.wait_for_packet()

    # Perform a handshake to terminate a connection
    def disconnect(self):
        print("Trying to disconnect (server-sided):")
        while not self._Disconnected:
            Fin_packet_bytes = self._PacketList[0]
            self._PacketList.pop(0)
            if Packet.unpack_packet(Fin_packet_bytes).getHeader().getFlags(
            ).flag_to_int() == 1 and self._btcpsocket.CheckChecksum(
                    Fin_packet_bytes):
                flags = Flags(0, 1, 1)
                Fin_packet = Packet.unpack_packet(Fin_packet_bytes)
                ack_number = Fin_packet.getHeader().getSynNumber() + 1
                header = Header(
                    random.getrandbits(15) & 0xffff, ack_number, flags,
                    self._window, 0, 0)
                Fin_Ack_packet = Packet(header, "fin_ack")
                self._lossy_layer.send_segment(Fin_Ack_packet.pack_packet())
                self._Disconnected = True
                print("Server disconnection successful")
            else:
                print(
                    "Retrying disconnection, the checksum of the packet is invalid or the packet is not a fin packet"
                )
                self.wait_for_packet()

    # Write the content of the received packets to a specified file (default output.file)
    def packet_to_file(self, file):
        content = ""
        for i in range(0, len(self._OutputList)):
            content += Packet.unpack_packet(
                self._OutputList[i]).getPayload().decode()
        f = open(file, 'w')
        f.write(content)
        f.flush()
        f.close()

    # Clean up any state and close the socket
    def close(self):
        self._lossy_layer.destroy()
Exemple #8
0
class BTCPClientSocket(BTCPSocket):
    def __init__(self, window, timeout, printSegments, maxRetries):
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)
        self._printSegments = printSegments
        self._currentState = "disconnected"
        self._maxRetries = maxRetries
        self._numberOfRetries = 0
        self._nextSeqNum = 0
        self._ackNum = 0
        self._sendBase = 0
        self._initialSequenceNumber = 0
        self._timer = 0
        self._timerLock = threading.Lock()
        self._disconnected = threading.Event()
        self._connected = threading.Event()
        self._entireFileAcknowledged = threading.Event()

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        if self._printSegments:
            print("Client has received: ")
            super().print_segment(segment[0])
        seqnum, acknum, ACK, SYN, FIN, windowsize, cksumHasSucceeded, data = super(
        ).breakdown_segment(segment[0])
        if (not cksumHasSucceeded):
            return
        self._window = windowsize  #to make sure we do not have a window size of 0
        if (self._window == 0):
            self._window = 1

        if (self._currentState == "connecting"):
            if (SYN and (not FIN) and ACK):
                self._currentState = "connected"
                self._nextSeqNum = acknum
                self._ackNum = seqnum + 1
                self._sendBase = acknum
                self.stopTimer()
                self._connected.set()
        elif (self._currentState == "connected"):
            if ((not SYN) and (not FIN) and ACK):
                #an ACK has been received
                if (acknum == self._lastSegment):
                    self._entireFileAcknowledged.set()
                    self.stopTimer()
                    self._sendBase = acknum + 1
                elif (acknum >= self._sendBase):
                    self._sendBase = acknum + 1
                    self.startTimer()
                self.sendSegmentsInWindow()
        elif (self._currentState == "disconnecting"):
            if ((not SYN) and FIN and ACK):
                self.stopTimer()
                self._disconnected.set()

    # Perform a three-way handshake to establish a connection
    def connect(self):
        randomSeqNum = random.randint(
            0, MAX_16BITS
        )  # Creating a random 16 bit value for the sequence number
        self._nextSeqNum = randomSeqNum
        self.sendSegment(randomSeqNum, 0, SYN=True)
        self._currentState = "connecting"
        self._numberOfRetries = 1
        self.startTimer()
        self._connected.wait(
        )  # wait until the connection is established to return to the app
        return self._currentState == "connected"  # return if the connection establishment succeeded

    def sendSegment(self,
                    seqnum=0,
                    acknum=0,
                    ACK=False,
                    SYN=False,
                    FIN=False,
                    windowsize=0,
                    data: bytes = b''):
        newsegment = self.buildsegment(seqnum,
                                       acknum,
                                       ACK=ACK,
                                       SYN=SYN,
                                       FIN=FIN,
                                       windowsize=windowsize,
                                       data=data)
        if self._printSegments:
            print("The client has sent a segment with SEQ ", seqnum)
        self._lossy_layer.send_segment(newsegment)

    # Send data originating from the application in a reliable way to the server
    def send(self, data):
        self._sendPackets = []
        for i in range(0, len(data), 1008):  #splits it in parts of 1008 bytes
            self._sendPackets.append(data[i:i + 1008])
        self._lastSegment = self._sendBase + len(self._sendPackets) - 1
        self._initialSequenceNumber = self._sendBase
        self.startTimer()
        self.sendSegmentsInWindow()
        self._entireFileAcknowledged.wait()

    def startTimer(self):
        self.stopTimer(
        )  # in case a different timer is running currently, cancel it
        self._timerLock.acquire()
        self._timer = threading.Timer(self._timeout / 1000, self.timeout)
        self._timer.start()
        self._timerLock.release()

    def stopTimer(self):
        if (self._timer == 0):  # timer has not yet been initialized
            return
        self._timerLock.acquire()
        self._timer.cancel()
        self._timerLock.release()

    def sendSegmentsInWindow(self):
        while (self._nextSeqNum <= self._sendBase + self._window
               and self._nextSeqNum <= self._lastSegment):
            if (self._nextSeqNum == self._initialSequenceNumber):
                self.sendSegment(
                    self._nextSeqNum,
                    self._ackNum,
                    ACK=True,
                    data=self._sendPackets[self._nextSeqNum -
                                           self._initialSequenceNumber])
            else:
                self.sendSegment(
                    self._nextSeqNum,
                    data=self._sendPackets[self._nextSeqNum -
                                           self._initialSequenceNumber])
            self._nextSeqNum += 1

    def timeout(self):
        if (self._currentState == "connecting"):
            if (self._numberOfRetries > self._maxRetries):
                self._connected.set()
                return
            self._numberOfRetries += 1
            randomSeqNum = random.randint(
                0, MAX_16BITS
            )  # Creating a random 16 bit value for the sequence number
            self._nextSeqNum = randomSeqNum
            self.sendSegment(randomSeqNum, 0, SYN=True)
        elif (self._currentState == "connected"):
            if (self._printSegments):
                print("a timeout has occured, we restart with seqnum : ",
                      self._sendBase, ', which is package number ',
                      self._sendBase - self._initialSequenceNumber)
            self._nextSeqNum = self._sendBase
            self.sendSegmentsInWindow()
        elif (self._currentState == "disconnecting"):
            if (self._numberOfRetries > self._maxRetries):
                self._disconnected.set()
            self._numberOfRetries += 1
            self.sendSegment(self._nextSeqNum, FIN=True)
        self.startTimer()

    # Perform a handshake to terminate a connection
    def disconnect(self):
        self._currentState = "disconnecting"
        self._numberOfRetries = 1
        self.startTimer()
        self.sendSegment(self._nextSeqNum, FIN=True)
        self._disconnected.wait()
        self._currentState = "disconnected"

    # Clean up any state
    def close(self):
        self._lossy_layer.destroy()
class BTCPServerSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self._lossy_layer = LossyLayer(self, SERVER_IP, SERVER_PORT, CLIENT_IP, CLIENT_PORT)

        # Boolean variable that determines whether the server has a connection to the client
        self._connected_to_client = False

        # Boolean variable that is true when the server is listening for the clients initiation of the three-way handshake
        self._listening = False

        # Global variables that keep track of the sequence and the acknowledgement number that are send during
        # the three-way handshake
        self._x_value = 0
        self._y_value = 0

        # Selective repeater
        self._selective_repeater = SelectiveRepeaterReceiver(self._lossy_layer, self._window)

    # Called by the lossy layer from another thread whenever a segment arrives
    def lossy_layer_input(self, rec_data):
        (header, packet) = rec_data
        seg_info = unpack_segment(header)
        (seq_number, ack_number, (ack, syn, fin), window, data_length, checksum, data) = seg_info
        if syn and not ack and not fin:
            if self._listening:
                self.accept_syn_packet(seg_info)
        if not syn and ack and not fin:
            if ack_number != self._y_value+1:
                print("acknowledgement number incorrect:", ack_number, self._y_value+1)
            else:
                print("connected to client.")
                self._connected_to_client = True
                self._listening = False
        if not syn and not ack and fin:
            self.finish_client()
        if not syn and not ack and not fin:
            self._selective_repeater.ReceivePacket(seg_info)

    # Wait for the client to initiate a three-way handshake
    def accept(self):
        self._listening = True

    # accept a packet from the client with the syn flag and do the server part of the three-way handshake
    def accept_syn_packet(self, seg_info):
        (seq_number, ack_number, (ack, syn, fin), window, data_length, checksum, data) = seg_info
        self._x_value = seq_number
        self._y_value = random.randint(0, MAX_VALUE_16_BIT_INTEGER + 1)
        ack_syn_fin = flags_to_binary(True, True, False)
        segment = Segment(self._y_value, self._x_value + 1, ack_syn_fin, self._window, 0, 0, None)
        data = segment.create_segment()
        self._lossy_layer.send_segment(data)

    # Second step of termination handshake
    def finish_client(self):
        ack_syn_fin = flags_to_binary(True, False, True)
        segment = Segment(0, 0, ack_syn_fin, 0, 0, 0, None)
        data = segment.create_segment()
        self._lossy_layer.send_segment(data)
        self._connected_to_client = False
        print("disconnected to client")

    # Send any incoming data to the application layer
    def recv(self, output):
        data = bytes()
        collected_data = bytes()

        # Wait for data to be delivered
        while len(data) == 0:
            data = self._selective_repeater.DeliverData()

        # Write bytes to file with path "output"
        f = open(output, 'wb')
        f.write(data)
        f.close()
        print("Data Received")

    def clean(self):
        self._listening = False
        self._x_value = 0
        self._y_value = 0
        self._selective_repeater = SelectiveRepeaterReceiver(self._lossy_layer, self._window)

    # Clean up any state
    def close(self):
        self._listening = False
        self._lossy_layer.destroy()
        self._x_value = 0
        self._y_value = 0
Exemple #10
0
class BTCPClientSocket:
    def __init__(self, timeout):
        self._lossy_layer = LossyLayer(self, CLIENT_IP, CLIENT_PORT, SERVER_IP,
                                       SERVER_PORT)

        self._timeout = timeout / 100000  # The timeout for one segment in seconds.
        self._timer = None  # The timer used to detect a timeout.
        self._seq_num = None  # The initial sequence number, initialized during establishment.

        # Variables for sending data over to the server.
        self._send_flag = None  # A flag to signify when to stop all the threads.
        self._seg_tries = None  # The amount of tries every segment gets.

        self._segments = None  # All segments which are to be send plus the amount of tries left for every segment.
        self._status = None  # The status for every segment: 0 not send, 1 send not ACKed, 2 timeout, 3 ACKed.
        self._pending = None  # The time at which certain segments are send, contains (seq_num, time send).
        self._acks = None  # All of the acknowledgements which need to be processed.

        self._send_base = None  # The index for self._segments up to which all segments are ACKed.
        self._window_size = None  # The window size of the server, initialized during establishment.

        self._status_lock = None  # Ensure that changing the status list is done thread safe.
        self._pending_lock = None  # Ensure that changing the pending list is done thread safe.
        self._send_base_lock = None  # Ensure that changing the send base is done thread safe.

        # Variables for the connection establishment phase.
        self._syn_tries = None  # The number of tries to establish a connection.
        self._connected = None  # A boolean to signify if the connection was successful, returned by connect().
        self._connected_flag = None  # An event to signify when the connection is established.

        # Variables for the connection termination phase.
        self._fin_tries = None  # The number of tries to terminate a connection.
        self._finished = None  # A boolean to signify if the closing was (ab)normal, returned by disconnect().
        self._finished_flag = None  # An event to signify when the connection is terminated.

    # Called by the lossy layer from another thread whenever a segment arrives.
    def lossy_layer_input(self, segment):
        try:
            seq_num, ack_num, flags, window_size, data = bytes_to_ascii(
                segment)
            if flags[0] and flags[1]:  # ACK & SYN
                self._handle_syn(seq_num, ack_num, window_size)
            elif flags[0] and flags[2]:  # ACK & FIN
                self._handle_fin()
            elif flags[0]:  # ACK
                self._handle_ack(ack_num, window_size)
        except ValueError:  # Incorrect checksum or data length.
            pass

    # Perform a three-way handshake to establish a connection.
    def connect(self):
        # Create the initial sequence number and amount of tries to establish a connection and the connected flag.
        self._connected_flag = threading.Event()
        self._connected = False
        self._syn_tries = 30
        self._seq_num = random.randint(0, 0xffff)

        # Send the first segment to the server.
        segment = ascii_to_bytes(self._seq_num, 0, [False, True, False], 0,
                                 b'')
        self._lossy_layer.send_segment(segment)

        # Create and start a timer for the connection establishment phase.
        self._timer = threading.Timer(self._timeout, self._handle_syn_timeout)
        self._timer.start()

        # Wait until the connection handshake is done.
        self._connected_flag.wait()
        return self._connected

    # Send data originating from the application in a reliable way to the server.
    def send(self, data):
        data = data.encode()

        # Initialize all the variables.
        self._send_flag = threading.Event()
        self._seg_tries = 30
        self._send_base = 0

        self._segments = [[segment, self._seg_tries]
                          for segment in create_segments(data, self._seq_num)]
        self._status = [0] * len(self._segments)
        self._pending = []
        self._acks = []

        self._status_lock = threading.Lock()
        self._pending_lock = threading.Lock()
        self._send_base_lock = threading.Lock()

        # Start the acknowledgement loop in a new thread.
        acks = threading.Thread(target=self._ack_loop)
        acks.start()

        # Start the timer in a new thread.
        timer = threading.Thread(target=self._timer_loop)
        timer.start()

        # Send all of the segments, returns if all are send.
        success = self._send_loop()

        # Set the _send_flag so the timer will stop.
        self._send_flag.set()
        acks.join()
        timer.join()

        # Return if the sending of all the segments was successful.
        return success

    # Perform a handshake to terminate a connection.
    def disconnect(self):
        self._finished_flag = threading.Event()
        self._finished = False
        self._fin_tries = 15

        # Send a FIN to the server.
        segment = ascii_to_bytes(0, 0, [False, False, True], 0, b'')
        self._lossy_layer.send_segment(segment)

        # Create and start a timer for the connection termination phase.
        self._timer = threading.Timer(self._timeout, self._handle_fin_timeout)
        self._timer.start()

        # Wait until the termination handshake is done.
        self._finished_flag.wait()
        return self._finished

    # Clean up any state.
    def close(self):
        self._lossy_layer.destroy()

    def _handle_syn(self, seq_num, ack_num, window_size):
        if ack_num == self._seq_num + 1:
            self._timer.cancel()
            self._window_size = window_size
            self._seq_num += 1

            # Send an ACK back to the server.
            segment = ascii_to_bytes(self._seq_num, seq_num + 1,
                                     [True, False, False], 0, b'')
            self._lossy_layer.send_segment(segment)

            # Signal the connect() function that the connection is established.
            self._connected = True
            self._connected_flag.set()

    def _handle_syn_timeout(self):
        if self._syn_tries <= 0:
            # Signal the connect() function that the connection could not be established.
            self._connected_flag.set()
        else:
            self._syn_tries -= 1

            # Resend the initial segment.
            segment = ascii_to_bytes(self._seq_num, 0, [False, True, False], 0,
                                     b'')
            self._lossy_layer.send_segment(segment)

            # Restart the timeout timer.
            self._timer = threading.Timer(self._timeout,
                                          self._handle_syn_timeout)
            self._timer.start()

    def _handle_fin(self):
        self._timer.cancel()
        self._finished = True
        self._finished_flag.set()

    def _handle_fin_timeout(self):
        if self._fin_tries <= 0:
            # Signal the disconnect() function that the connection could not be normally terminated.
            self._finished_flag.set()
        else:
            self._fin_tries -= 1

            # Resend the initial segment.
            segment = ascii_to_bytes(0, 0, [False, False, True], 0, b'')
            self._lossy_layer.send_segment(segment)

            # Restart the timeout timer.
            self._timer = threading.Timer(self._timeout,
                                          self._handle_fin_timeout)
            self._timer.start()

    def _handle_ack(self, ack_num, window_size):
        self._acks.append((ack_num, window_size))

    def _ack_loop(self):
        while not self._send_flag.is_set():
            if self._acks:
                ack_num, window_size = self._acks.pop(0)
                # Update the window size and increase the send base by one if this was the next to be ACKed segment.
                self._window_size = window_size
                try:
                    self._send_base_lock.acquire()
                    if self._send_base == ack_num - self._seq_num:
                        self._send_base = ack_num - self._seq_num + 1
                finally:
                    self._send_base_lock.release()

                # Change the segment status to received ACK.
                try:
                    self._status_lock.acquire()
                    self._status[ack_num - self._seq_num] = 3  # ACKed flag
                finally:
                    self._status_lock.release()

                # Remove the ACKed segment from the pending list.
                try:
                    self._pending_lock.acquire()
                    self._pending = [(seq_num, _time)
                                     for (seq_num, _time) in self._pending
                                     if seq_num != ack_num]
                finally:
                    self._pending_lock.release()

    def _timer_loop(self):
        while not self._send_flag.is_set():
            try:
                self._status_lock.acquire()
                self._pending_lock.acquire()
                still_pending = []
                for (seq_num, time_send) in self._pending:
                    if time.time_ns(
                    ) // 1000 - time_send > self._timeout * 100000:
                        # A timeout occurred, change the status flag to timeout so it will be resend.
                        if self._status[self._seq_num -
                                        seq_num] != 3:  # not ACKed
                            self._status[self._seq_num -
                                         seq_num] = 2  # timeout flag
                    else:
                        still_pending.append((seq_num, time_send))
                self._pending = still_pending
            finally:
                self._pending_lock.release()
                self._status_lock.release()
            time.sleep(0.005)

    def _send_loop(self):
        while self._send_base < len(
                self._segments):  # There are segments left to get ACKed.
            # Check all the segments from the send base till the window if one can be send and then send ONE or none.
            try:
                self._send_base_lock.acquire()
                self._status_lock.acquire()
                for index, status in enumerate(
                        self._status[self._send_base:self._send_base +
                                     self._window_size]):
                    if (status == 0 or status == 2) and len(
                            self._pending
                    ) < self._window_size:  # not send or timeout
                        # Check if the amount of tries for this segment is exceeded.
                        if self._segments[self._send_base + index][1] <= 0:
                            return False
                        else:
                            self._segments[self._send_base + index][1] -= 1

                        # Send the segment, add it to the pending segments and update the status.
                        self._lossy_layer.send_segment(
                            self._segments[self._send_base + index][0])
                        self._status[self._send_base +
                                     index] = 1  # send but not ACKed flag
                        try:
                            self._pending_lock.acquire()
                            self._pending.append(
                                (self._send_base + index + self._seq_num,
                                 time.time_ns() // 1000))
                        finally:
                            self._pending_lock.release()
            finally:
                self._status_lock.release()
                self._send_base_lock.release()
        return True
Exemple #11
0
class BTCPServerSocket(BTCPSocket):
    def __init__(self, window, timeout):
        super().__init__(window, timeout)
        self.window = window
        self.timeout = timeout
        self.thread_executor = ThreadPoolExecutor(max_workers=window)
        self._lossy_layer = LossyLayer(self, SERVER_IP, SERVER_PORT, CLIENT_IP,
                                       CLIENT_PORT)
        self.state = State.CLOSED

    # Called by the lossy layer from another thread whenever a segment arrives
    def lossy_layer_input(self, segment):
        segment = btcp.packet.unpack_from_socket(segment)

        if not segment.confirm_checksum() or self.state == State.CLOSED:
            # discard segment
            pass
        if segment.packet_type() == "SYN":
            self.state = State.SYN_RECVD
            self.thread_executor.submit(self.handshake_response_thread,
                                        segment)
        elif segment.packet_type() == "ACK":
            self.state = State.HNDSH_COMP
        elif segment.packet_type() == "FIN":
            self.state = State.FIN_RECVD
            self.close_connection()
            self.close()
        else:
            pass

    # Wait for the client to initiate a three-way handshake
    def accept(self):
        self.state = State.LISTEN

    # Send any incoming data to the application layer
    def recv(self):
        pass

    # Clean up any state
    def close(self):
        self.thread_executor.shutdown(wait=True)
        print("DESTROY SERVER SOCKET")
        self._lossy_layer.destroy()

    #
    def handshake_response_thread(self, segment):
        seq_nr = randint(0, 65535) - segment.get_seq_nr()
        ack_nr = segment.get_seq_nr() + 1
        segment.up_seq_nr(seq_nr)
        segment.up_ack_nr(ack_nr)
        segment.set_flags(True, True, False)
        self._lossy_layer.send_segment(segment.pack())
        self.state = State.SYN_SEND
        while True:  # as long as no ACK handshake segment is received
            time.sleep(self.timeout / 1000)
            if (self.state != State.HNDSH_COMP):
                self._lossy_layer.send_segment(segment.pack())
            else:
                break

    #
    def close_connection(self):
        segment = TCPpacket()
        segment.set_flags(True, False, True)
        send_segment = segment.pack()
        self._lossy_layer.send_segment(send_segment)