class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        # for now I'm just sending the raw application-level data in one UDP payload
        self.socket.sendto(data_bytes, (self.dst_ip, self.dst_port))

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!

        # this sample code just calls the recvfrom method on the LossySocket
        data, addr = self.socket.recvfrom()
        # For now, I'll just pass the full UDP payload to the app
        return data

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        pass
Example #2
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

    def send(self, data_bytes: bytes):
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        # for now I'm just sending the raw application-level data in one UDP payload
        i=0
        j=len(data_bytes)
        if(j>1472):
            while((j-i)>1472):
                self.socket.sendto(data_bytes[i:i+1472], (self.dst_ip, self.dst_port))
                i=i+1472
            self.socket.sendto(data_bytes[i:j], (self.dst_ip, self.dst_port))
        else:
            self.socket.sendto(data_bytes, (self.dst_ip, self.dst_port))

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        
        # this sample code just calls the recvfrom method on the LossySocket
        data, addr = self.socket.recvfrom()
        # For now, I'll just pass the full UDP payload to the app
        return data
Example #3
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.recvBuff = {}
        self.seqNum = 0
        self.recNum = 0

    def send(self, data_bytes: bytes):
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        # for now I'm just sending the raw application-level data in one UDP payload
        i = 0
        j = len(data_bytes)
        seq = (str(self.seqNum) + ' ').encode()

        if ((j + len(seq)) > 1472):
            while ((j - i - len(seq)) > 1472):
                self.socket.sendto(seq + data_bytes[i:i + 1472 - len(seq)],
                                   (self.dst_ip, self.dst_port))
                i = i + 1472 - len(seq)
                self.seqNum = 1 + self.seqNum
                seq = (str(self.seqNum) + ' ').encode()
            self.socket.sendto(seq + data_bytes[i:j],
                               (self.dst_ip, self.dst_port))
        else:
            self.socket.sendto(seq + data_bytes, (self.dst_ip, self.dst_port))
            self.seqNum += 1

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!

        # this sample code just calls the recvfrom method on the LossySocket

        while (True):
            data, addr = self.socket.recvfrom()
            output = ''.encode()
            d = data.decode().split(' ', 1)
            if d[0] == str(self.recNum):
                output = d[1].encode()
                self.recNum = self.recNum + 1
                while (self.recNum in self.recvBuff):
                    output += self.recvBuff[self.recNum]
                    self.recNum += 1
                return output
            else:
                self.recvBuff[self.recNum] = d[1].encode()
Example #4
0
class Streamer:
    send_sequence_number = 0
    receive_sequence_number = 0

    receive_buffer = {}
    send_buffer = {}

    self_half_closed = False
    remote_closed = False
    closed = False

    executor = None
    recv_thread = None
    send_thread = None

    last_fin_ack_sent = None

    chunk_size = 1446
    time_out_seconds = 0.25
    fin_grace_period = 2
    default_wait_seconds = 0.001

    alpha = 0.1
    beta = 0.1
    DevRTT = 0.01
    EstimatedRTT = 0.15

    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.executor = ThreadPoolExecutor(max_workers=2)
        self.recv_thread = self.executor.submit(self.recv_async)
        self.send_thread = self.executor.submit(self.send_async)

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""

        chunk_index = 0
        while chunk_index * self.chunk_size < len(data_bytes):
            chunk_start_index = chunk_index * self.chunk_size
            chunk_end_index = min(len(data_bytes),
                                  (chunk_index + 1) * self.chunk_size)

            packet = TCPPacket(
                sequence_number=self.send_sequence_number,
                data_bytes=data_bytes[chunk_start_index:chunk_end_index])

            self.send_buffer[self.send_sequence_number] = (packet, 0)

            self.send_sequence_number += 1
            chunk_index += 1

    def send_ack(self, acknowledgement_number: int):
        packet = TCPPacket(sequence_number=acknowledgement_number, ack=True)
        self.socket.sendto(packet.pack(), (self.dst_ip, self.dst_port))

    def send_fin(self, sequence_number: int):
        packet = TCPPacket(fin=True, sequence_number=sequence_number)
        self.send_buffer[packet.sequence_number] = (packet, time.time())

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""

        while self.receive_sequence_number not in self.receive_buffer:
            time.sleep(self.default_wait_seconds)

        # curr = self.receive_buffer.pop(0)
        curr = self.receive_buffer[self.receive_sequence_number]
        del self.receive_buffer[self.receive_sequence_number]
        self.receive_sequence_number += 1

        return curr.data_bytes

    def send_async(self):
        while not self.self_half_closed:
            try:
                copy_send_buffer = self.send_buffer.copy()
                for val in copy_send_buffer:
                    if isinstance(copy_send_buffer[val], tuple):
                        packet, time_sent = copy_send_buffer[val]
                        if time_sent is not None:
                            if time.time() - time_sent > self.time_out_seconds:
                                self.socket.sendto(
                                    packet.pack(),
                                    (self.dst_ip, self.dst_port))
                                self.send_buffer[packet.sequence_number] = (
                                    packet, time.time())
                        else:
                            del self.send_buffer[packet.sequence_number]
                        pass
            except Exception as ex:
                pass
                # print("Sender died")
                # traceback.print_exc(file=sys.stdout)
            time.sleep(self.default_wait_seconds)
        return True

    def recv_async(self):
        while not self.closed:
            try:
                data, addr = self.socket.recvfrom()
                if data is not None and data != b'':
                    packet = TCPPacket()
                    packet.unpack(data)

                    calculated_checksum = packet.get_checksum(data[16:])
                    if calculated_checksum == packet.checksum:
                        if packet.ack:
                            ack_packet = TCPPacket(
                                sequence_number=packet.sequence_number)
                            if packet.sequence_number % 20 == 0 and packet.sequence_number in self.send_buffer and \
                                    self.send_buffer[packet.sequence_number][
                                        1] is not None:
                                self.calculate_new_timeout(
                                    time.time() -
                                    self.send_buffer[packet.sequence_number][1]
                                )
                            self.send_buffer[packet.sequence_number] = (
                                ack_packet, None)
                        elif packet.fin:
                            # print("FIN RECEIVED", time.time())
                            self.send_ack(
                                acknowledgement_number=packet.sequence_number)
                            self.last_fin_ack_sent = time.time()
                            # if not self.self_half_closed:
                            #     # print("REMOTE CLOSED")
                            #     self.remote_closed = True
                        else:
                            self.send_ack(
                                acknowledgement_number=packet.sequence_number)
                            if packet.sequence_number not in self.receive_buffer:
                                self.receive_buffer[
                                    packet.sequence_number] = packet
            except Exception as e:
                pass
                # print(e)
                # self.remote_closed = True
        return True

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""

        while len(self.send_buffer) > 0:
            # print(self.send_buffer)
            time.sleep(self.default_wait_seconds)

        # print("SENDING FIN")

        self.send_fin(sequence_number=self.send_sequence_number)
        self.send_sequence_number += 1

        while len(self.send_buffer) > 0:
            time.sleep(self.default_wait_seconds)

        # print("FIN ACCEPTED", self.remote_closed)
        self.self_half_closed = True

        while not self.remote_closed:
            if self.last_fin_ack_sent is not None:
                if time.time(
                ) - self.last_fin_ack_sent > self.fin_grace_period:
                    # print("FIN COMPLETE")
                    self.remote_closed = True
                else:
                    time.sleep(self.default_wait_seconds)

        self.closed = True
        self.socket.stoprecv()

        while not self.send_thread.done():
            self.send_thread.cancel()
            time.sleep(self.default_wait_seconds)

        while not self.recv_thread.done():
            self.recv_thread.cancel()
            time.sleep(self.default_wait_seconds)

        self.executor.shutdown()
        # your code goes here, especially after you add ACKs and retransmissions.
        pass

    def calculate_new_timeout(self, sample_rtt: float):
        self.EstimatedRTT = (
            1 - self.alpha) * self.EstimatedRTT + self.alpha * sample_rtt
        self.DevRTT = (1 - self.beta) * self.DevRTT + self.beta * abs(
            sample_rtt - self.EstimatedRTT)
        self.time_out_seconds = self.EstimatedRTT + self.DevRTT * 4
class Streamer:

    global WINDOW_SIZE
    global DATS_B4_ACK

    global MS_FOR_SENDER

    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.current_seq = 0  # The current packetnum to be sent and to be expected
        self.last_ACK = -1  # The last packetnum that was acked
        self.sender_index = 0  # For labeling the packets to be sent

        self.data_to_send = {}  # Buffer to send items
        self.buffer = {}  # Buffer for recieved items

        self.listening = True
        self.FIN = False
        self.FINACK = False

        self.sending = False
        self.receiving = False

        self.wait_to_send = 0

        self.last_sent = 0
        self.send_window = 0

        self.timing = True

        executor = futures.ThreadPoolExecutor(max_workers=2)
        executor.submit(self.listen)
        executor.submit(self.sender)

    def send(self, data_bytes: bytes) -> None:

        while self.receiving:
            sleep(0.01)

        self.sending = True

        header_length = 50
        max_packet_length = 1472

        data_length = max_packet_length - header_length

        for data in self.break_string(data_bytes, data_length):
            sleep(0.05)
            h = hashlib.md5()

            packet = (' DAT ' + str(self.current_seq) + '~').encode() + data
            h.update(packet)
            packet = h.hexdigest().encode() + packet
            #print("Sent: %s at %s" % (packet, self.millis()))
            self.data_to_send[self.current_seq] = packet

            self.socket.sendto(packet, (self.dst_ip, self.dst_port))
            self.send_window += 1

            if self.send_window > WINDOW_SIZE:
                self.last_sent = self.current_seq - WINDOW_SIZE

            while self.send_window > WINDOW_SIZE:
                self.send_window = self.current_seq - self.last_ACK

            self.current_seq += 1

        self.sending = False

    def sender(self) -> None:
        ms = 0
        while self.timing:
            if self.last_sent > self.last_ACK:
                sleep(0.01)
                ms += 1
                if ms >= MS_FOR_SENDER:
                    self.resend(self.last_sent)
                    ms = 0
            elif self.last_sent <= self.last_ACK:
                self.last_sent = self.last_ACK + 1
            if self.FINACK:
                break

    def resend(self, packet_num: int) -> None:
        try:
            packet = self.data_to_send[packet_num]
        except Exception as e:
            pass
        else:
            #print("Resent: %s at %s" % (packet, self.millis()))
            self.socket.sendto(packet, (self.dst_ip, self.dst_port))

    def send_ACK(self, number: int) -> None:
        h = hashlib.md5()
        acknowledgement = (' ACK ' + str(number) + '~').encode()
        h.update(acknowledgement)
        acknowledgement = h.hexdigest().encode() + acknowledgement
        #print("Sent: %s at %s" % (acknowledgement, self.millis()))
        self.socket.sendto(acknowledgement, (self.dst_ip, self.dst_port))

    def send_FIN(self) -> None:
        h = hashlib.md5()
        finish = (' FIN ' + str(self.current_seq) + '~').encode()
        h.update(finish)
        finish = h.hexdigest().encode() + finish
        #print("Sent: %s at %s" % (finish, self.millis()))
        self.socket.sendto(finish, (self.dst_ip, self.dst_port))

    def send_FINACK(self) -> None:
        h = hashlib.md5()
        finishack = (' FINACK ' + str(self.current_seq) + '~').encode()
        h.update(finishack)
        finishack = h.hexdigest().encode() + finishack
        #print("Sent: %s at %s" % (finishack, self.millis()))
        self.socket.sendto(finishack, (self.dst_ip, self.dst_port))

    def recv(self) -> bytes:

        while self.sending:
            sleep(0.01)

        self.receiving = True

        while True:
            if self.current_seq in self.buffer:
                data = self.buffer.pop(self.current_seq)
                self.last_ACK = self.current_seq - 1
                self.current_seq += 1
                break

        #print("Retrieved: %s at %s" % (data, self.millis()))

        self.receiving = False

        return data

    def listen(self) -> None:
        while self.listening:

            try:
                totpacket, addr = self.socket.recvfrom()

            except Exception as e:
                print("Listener died: " + str(e))

            else:
                #print("Got: %s at %s" % (totpacket, self.millis()))
                h = hashlib.md5()

                hash, packet = totpacket.split(b' ', 1)
                h.update(b' ' + packet)
                broke = False

                try:
                    if hash.decode() != h.hexdigest():
                        broke = True
                except Exception as e:
                    continue
                else:
                    if not broke:
                        header, data = packet.split(b'~')
                        packet_type, seq_num = header.split(b' ')
                        seq_num = int(seq_num)

                        if packet_type == b'DAT':
                            self.wait_to_send += 1

                            if seq_num == self.last_ACK + 1:
                                self.buffer[seq_num] = data
                                self.last_ACK += 1

                            elif seq_num > self.last_ACK + 1:
                                self.buffer[seq_num] = data

                            if self.wait_to_send >= DATS_B4_ACK:
                                self.send_ACK(self.last_ACK)
                                self.wait_to_send = 0

                        elif packet_type == b'ACK':

                            if seq_num > self.last_ACK:
                                self.last_ACK = seq_num

                            else:
                                pass

                        elif packet_type == b'FIN':
                            self.FIN = True

                        elif packet_type == b'FINACK':
                            self.FIN = True
                            self.FINACK = True

    def stop_listening(self) -> None:
        self.listening = False
        self.socket.stoprecv()

    def close(self) -> None:

        while self.sending or self.receiving:
            sleep(0.01)

        self.send_FIN()
        miliseconds = 0
        while not self.FIN:
            if (miliseconds % 50) == 49:
                self.send_FIN()
            sleep(0.01)
            miliseconds += 1

        self.send_FINACK()
        miliseconds = 0
        while not self.FINACK:
            if (miliseconds % 50) == 49:
                self.send_FINACK()
            if miliseconds >= 250:
                break
            sleep(0.01)
            miliseconds += 1

        self.timing = False
        sleep(1)

        self.stop_listening()

        self.data_to_send = {}  # Buffer to send items
        self.buffer = {}  # Buffer for recieved items

        self.listening = True
        self.FIN = False
        self.FINACK = False

        self.sending = False
        self.receiving = False

        self.current_seq = -1  # The current packetnum to be sent and to be expected
        self.last_ACK = -1  # The last packetnum that was acked
        self.sender_index = 0  # For labeling the packets to be sent

    def parse_packet(self, packet: bytes) -> None:
        pass

    def checksum(self, string: bytes) -> str:
        pass

    def wait_for_ACK(self, packet_num: int) -> None:

        miliseconds = 0
        timeout = 25
        while (self.last_ACK < self.current_seq and miliseconds < timeout):
            sleep(0.01)
            miliseconds += 1

        if miliseconds >= timeout:
            self.resend(packet_num)

    def break_string(self, string: bytes, length: int) -> list:
        return (string[0 + i:length + i]
                for i in range(0, len(string), length))

    def millis(self):
        return int(round(time.time() * 1000))
Example #6
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port
        self.seq = 0
        self.buffer = dict()
        self.prev_recv = -1
        self.closed = False
        self.ack = False
        executor = ThreadPoolExecutor(max_workers=1)
        executor.submit(self.listener)

    def hasher(self, incoming: bytes) -> bytes:
        h = hashlib.md5(incoming).digest()
        return h

    def send_helper(self, data_bytes: bytes, retransmit):
        length = len(data_bytes)
        #i'm changing the packet structure to fit the hash [16 byte s for storing hash]
        max_packet = int(1400 - calcsize('ic16s'))

        if length <= max_packet:
            #print(calcsize('sic16s'))
            #print('send size', len(pack(str(length) + 'sic16s', data_bytes, self.seq, b'd', self.hasher(data_bytes))))

            self.socket.sendto(
                pack(
                    str(length) + 'sic16s', data_bytes, self.seq, b'd',
                    self.hasher(
                        pack(str(length) + 'sic', data_bytes, self.seq,
                             b'd'))), (self.dst_ip, self.dst_port))

            if not retransmit:
                self.wait_ack(data_bytes)
            #     self.seq = self.seq + 1
        else:
            chunk = max_packet
            while chunk < length:
                # print('send size', len(pack(str(max_packet) + 'sic', data_bytes[chunk - max_packet:chunk], self.seq, b'd')))
                self.socket.sendto(
                    pack(
                        str(max_packet) + 'sic16s',
                        data_bytes[chunk - max_packet:chunk], self.seq, b'd',
                        self.hasher(
                            pack(
                                str(max_packet) + 'sic',
                                data_bytes[chunk - max_packet:chunk], self.seq,
                                b'd'))), (self.dst_ip, self.dst_port))

                if not retransmit:
                    self.wait_ack(data_bytes[chunk - max_packet:chunk])
                #     self.seq = self.seq + 1
                chunk = chunk + max_packet
            # print('send size', len(pack(str(max_packet) + 'sic', data_bytes[chunk - max_packet:], self.seq, b'd')))
            self.socket.sendto(
                pack(
                    str(max_packet) + 'sic16s',
                    data_bytes[chunk - max_packet:], self.seq, b'd',
                    self.hasher(
                        pack(
                            str(max_packet) + 'sic',
                            data_bytes[chunk - max_packet:], self.seq, b'd'))),
                (self.dst_ip, self.dst_port))

            if not retransmit:
                self.wait_ack(data_bytes[chunk - max_packet:])
            #     self.seq = self.seq + 1

    def wait_ack(self, data_bytes: bytes):
        while not self.ack:
            time.sleep(0.25)
            print(self.ack)
            if not self.ack:
                self.send_helper(data_bytes, True)
                print("sending {} again bc dropped".format(data_bytes))
                continue
            else:
                self.seq = self.seq + 1
                break
        self.ack = False

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!
        self.send_helper(data_bytes, False)

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!

        # this sample code just calls the recvfrom method on the LossySocket
        while not str(self.prev_recv + 1) in self.buffer:
            print("Looking for", self.prev_recv + 1)
            time.sleep(0.1)
            continue

        print('found it in here', self.buffer)
        self.prev_recv = self.prev_recv + 1
        return self.buffer.pop(str(self.prev_recv))

    def listener(self):
        while not self.closed:  # a later hint will explain self.closed
            try:
                data, addr = self.socket.recvfrom()
                if len(data) == 0:
                    continue
                # store the data in the receive buffer #len(data) - 5 --> - (5+16)
                print('received',
                      unpack(str(len(data) - 21) + 'sic16s', data)[0:3])
                data_bytes, seq_num, packet_type, data_hash = unpack(
                    str(len(data) - 21) + 'sic16s', data)

                #check corrupted data
                ref_hash = self.hasher(
                    pack(
                        str(len(data) - 21) + 'sic', data_bytes, seq_num,
                        packet_type))

                if ref_hash != data_hash:
                    #corrupted packet is dropped here
                    continue
                elif str(packet_type)[2] == 'a':
                    self.ack = True
                elif str(packet_type)[2] == 'd':
                    self.socket.sendto(
                        pack(
                            '2sic16s', b'aa', self.prev_recv, b'a',
                            self.hasher(
                                pack('2sic', b'aa', self.prev_recv, b'a'))),
                        (self.dst_ip, self.dst_port))
                    data_bytes = bytes(
                        data_bytes.decode('utf-8').rstrip('\0x00'),
                        encoding='utf-8')
                    self.buffer[str(seq_num)] = data_bytes
                else:
                    self.socket.sendto(
                        pack(
                            '2sic16s', b'aa', self.prev_recv, b'a',
                            self.hasher(
                                pack('2sic', b'aa', self.prev_recv, b'a'))),
                        (self.dst_ip, self.dst_port))
                print('new buffer', self.buffer)
            except Exception as e:
                print("listener died!")
                print(e)

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        self.ack = False
        while not self.ack:
            self.socket.sendto(
                pack('2sic16s', b'ff', self.seq, b'f',
                     self.hasher(pack('2sic', b'ff', self.seq, b'f'))),
                (self.dst_ip, self.dst_port))
            time.sleep(0.25)
        time.sleep(2)
        self.closed = True
        self.socket.stoprecv()
        return
Example #7
0
class Streamer:

    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.segmentSize = 1472 # maximum size of a udp body
        self.headerSize = 16 # seq 4 bytes, ack 4 bytes, checksum 8 bytes
        self.watchDogTimeout = 10 # if not hearing from anything from remote after 10 seconds when the connection is not closed, watch dog will shut down the connection
        self.closeWaitTimeout = 3 # if no progress is made in recent 3 seconds, after user has called .close(), assume remote has exited

        self.seek = 0 # upper-level application has read until

        self.pushBuffer = {} # out-bound buffer. key is sequence number, value is body bytes
        self.pushLocalSeek = 0 # I have written until
        self.pushRemoteSeek = 0 # remote has read until

        self.pullBuffer = {} # in-bound buffer. key is sequence number, value is body bytes
        self.pullLocalSeek = 0 # I have read until
        self.pullRemoteSeek = 0 # remote has written until

        self.maxInFlightSegmentCount = 8 # out-bound worker sends data packets as many as
        self.lastEchoTimeStamp = float("inf") # last time stamp when hearing from remote side, even if corrupted. It proves that remote side is alive

        self.lock = threading.Lock() # lock on push buffer, push local seek, push remote seek, pull buffer, pull local seek, pull remote seek

        self.closed = False # if closed, workers stop while-loop

        self.executor = concurrent.futures.ThreadPoolExecutor()

        self.outBoundJob = self.executor.submit(self.outBoundWorker) # out-bound worker is only in charge of sending packets
        self.inBoundJob = self.executor.submit(self.inBoundWorker) # in-bound worker is only in charge of receiving packets and updating seeks
        self.watchDogJob = self.executor.submit(self.watchDogWorker) # watch dog worker closes the connection after not hearing from remote side for too long

    def send(self, data_bytes: bytes) -> None: # application wants to send something
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        # for now I'm just sending the raw application-level data in one UDP payload
        bodySize = self.segmentSize - self.headerSize

        for i in range(0, len(data_bytes), bodySize): # cut data into chunks of maximum body size
            body = data_bytes[i: i + bodySize]

            with self.lock:
                self.pushBuffer[self.pushLocalSeek] = body # do nothing but put the data in the send buffer
                self.pushLocalSeek += len(body) # update push buffer's local seek pointer further

    def outBoundWorker(self) -> None: # out-bound worker is in charge of sending data in push buffer, re-transmitting and sending heart beat packet when there are no data in push buffer

        while not self.closed:
            time.sleep(0.25) # every some time you should send something. If there is data, send data. If there is no data, send a heart beat

            with self.lock:
                if self.pushRemoteSeek < self.pushLocalSeek: # receiver did not acknowledged every segments we sent

                    for k in sorted(self.pushBuffer.keys()): # clear segments acknowledged by remote
                        v = self.pushBuffer[k]
                        if k + len(v) <= self.pushRemoteSeek:
                            self.pushBuffer.pop(k)

                    # aggregate small packets into large packets
                    data = bytearray()

                    for k in sorted(self.pushBuffer.keys()):
                        v = self.pushBuffer[k]
                        data.extend(v)

                    self.pushBuffer.clear()

                    for i in range(0, len(data), self.segmentSize - self.headerSize):
                        chunk = data[i: i + self.segmentSize - self.headerSize]
                        self.pushBuffer[i + self.pushRemoteSeek] = bytes(chunk)

                    for k in sorted(self.pushBuffer.keys())[: self.maxInFlightSegmentCount]:
                        v = self.pushBuffer[k]
                        self.sendSegment(k, self.pullLocalSeek, v)

                else: # no data in push buffer
                    self.sendAck(self.pushLocalSeek, self.pullLocalSeek) # send a heart beat then to let remote know we are alive

                # print("pull local seek:", self.pullLocalSeek)
                # print("pull remote seek:", self.pullRemoteSeek)
                # print("push local seek:", self.pushLocalSeek)
                # print("push remote seek:", self.pushRemoteSeek)

    def inBoundWorker(self) -> None: # background in-bound worker

        while not self.closed:
            if self.recvIntoBuffer():
                continue
            else:
                break

    def watchDogWorker(self) -> None: # watch dog worker shuts down the connection when it assumes that 

        while not self.closed:
            time.sleep(1)

            with self.lock:
                makingProgress = (time.time() - self.lastEchoTimeStamp) < self.watchDogTimeout

            if not makingProgress:
                # print("watchdog barks")
                # print("pull local seek:", self.pullLocalSeek)
                # print("pull remote seek:", self.pullRemoteSeek)
                # print("push local seek:", self.pushLocalSeek)
                # print("push remote seek:", self.pushRemoteSeek)
                self.close()

    def recvIntoBuffer(self) -> bool: # receive packet, decode packet, update pull buffer, pull remote seek, pull local seek, push remote seek
        data, addr = self.socket.recvfrom() # decode segment

        if not data:
            return False

        self.lock.acquire()
        self.lastEchoTimeStamp = time.time()

        try:
            decoded = self.decodeSegment(data) # if corrupted, will catch an exception here
        except ValueError:
            # print("corrupted.")
            # self.sendAck(self.pushLocalSeek, self.pullLocalSeek)
            self.lock.release()
            return True

        seq = decoded["seq"]
        ack = decoded["ack"]
        control = decoded["control"]
        body = decoded["body"]
        # print(">", seq, ack, control, body)

        self.pullRemoteSeek = max(self.pullRemoteSeek, seq + len(body)) # remote has written until
        self.pushRemoteSeek = max(self.pushRemoteSeek, ack) # remote has read until

        if body:
            self.pullBuffer[seq] = body

            if self.pullLocalSeek == self.pullRemoteSeek:
                pass
            else:
                seek = self.pullLocalSeek

                while True:
                    if seek not in self.pullBuffer:
                        break
                    else:
                        seek += len(self.pullBuffer[seek])

                self.pullLocalSeek = seek # we can acknowledge data until
        else: # no data
            pass

        self.lock.release()

        return True

    def sendAck(self, seq: int, ack: int) -> None:
        self.sendSegment(seq, ack)

    def sendSegment(self, seq: int, ack: int, body: bytes=b"") -> None:
        header = struct.pack(">ll", seq, ack)
        segment = header + body

        hasher = hashlib.sha1()
        hasher.update(segment)
        checksum = hasher.digest()[: 8] # checksum is calculated over seq, ack, body

        segment = header + checksum + body
        # print("<", seq, ack, checksum, body)
        self.socket.sendto(segment, (self.dst_ip, self.dst_port))

    def decodeSegment(self, data: bytes=b"") -> dict:
        seqack = data[: 8]
        control = data[8: 16] # checksum
        body = data[16: ]

        hasher = hashlib.sha1()
        hasher.update(seqack)
        hasher.update(body)
        checksum = hasher.digest()[: 8]

        if checksum != control:
            raise ValueError("Corrupted")

        seq, ack = struct.unpack(">ll", seqack)

        return {
            "seq": seq,
            "ack": ack,
            "control": control,
            "body": body,
        }

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        
        # this sample code just calls the recvfrom method on the LossySocket
        while True:
            if self.seek in self.pullBuffer: # if requested segment has already arrived

                with self.lock:
                    res = self.pullBuffer.pop(self.seek)
                    self.seek += len(res)

                break # feed body to upper layer immediately
            elif not self.closed: # if not arrived yet, wait
                self.recvIntoBuffer() # busy wait. could be optimized using thread conditional value
            else: # if closed
                raise Exception("Connection is already closed.")

        return res
        # For now, I'll just pass the full UDP payload to the app

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        while True:

            with self.lock:
                unsynced = self.pushLocalSeek != self.pushRemoteSeek or self.pullLocalSeek != self.pullRemoteSeek # remote side is not fully synced with local side
                makingProgress = (time.time() - self.lastEchoTimeStamp) < self.closeWaitTimeout # local side is anxious waiting to close, so timeout is smaller here
                # print("pull local seek:", self.pullLocalSeek)
                # print("pull remote seek:", self.pullRemoteSeek)
                # print("push local seek:", self.pushLocalSeek)
                # print("push remote seek:", self.pushRemoteSeek)

            if unsynced and makingProgress: # if remote and local are not fully synced but are at least making some progress (because remote is alive)
                time.sleep(0.1) # keep waiting
                # print("wait")
            else: # if fully synced or not remote is possibly dead
                # print("unsynced:", unsynced, "makingProgress:", makingProgress)
                break # close immediately

        self.socket.stoprecv()
        self.closed = True

        # print("pull buffer size:", len(self.pullBuffer))
        # print("push buffer size:", len(self.pushBuffer))
        # print("---")
        # print("pull local seek:", self.pullLocalSeek)
        # print("pull remote seek:", self.pullRemoteSeek)
        # print("push local seek:", self.pushLocalSeek)
        # print("push remote seek:", self.pushRemoteSeek)
Example #8
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
        and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        # Send info
        self.send_next_seq_n = 0
        self.acked_n = 0
        self.ACK_TIMEOUT = 0.25  # seconds
        self.send_q = queue.PriorityQueue()  # queue of InflightPacket

        # Recv info
        self.recv_expect_seq_n = 0
        self.recv_q = queue.PriorityQueue()

        # listener thread
        self.closed = False
        self.should_close = False

        executor = ThreadPoolExecutor(max_workers=2)
        executor.submit(self._listener)
        executor.submit(self._sender)

    def send(self, data_bytes: bytes, timeout_s=None) -> None:
        """Note that data_bytes can be larger than one packet."""
        timeout_s = timeout_s if timeout_s else self.ACK_TIMEOUT

        while data_bytes:
            if len(data_bytes) > PAYLOAD_SIZE:
                recv_buf_size = len(data_bytes) - PAYLOAD_SIZE
            else:
                recv_buf_size = 0

            send_buf = Packet(
                seq_n=self.send_next_seq_n,
                recv_buf_size=recv_buf_size,
                payload=data_bytes[:PAYLOAD_SIZE],
            ).to_bytes()
            self.send_q.put(
                InflightPacket(seq_n=self.send_next_seq_n,
                               start_time=None,
                               timeout_s=timeout_s,
                               packet=send_buf))
            self.send_next_seq_n += 1
            # print(
            # f"Sent (and recv'ed ack for): {data_bytes[:PAYLOAD_SIZE]}, remaining: {data_bytes[PAYLOAD_SIZE:]}"
            # )

            data_bytes = data_bytes[PAYLOAD_SIZE:]

    def _sender(self):
        """Sender background thread"""
        inflight_q: List[InflightPacket] = []  # actual inflight packets

        while not self.closed or not inflight_q.empty():
            # print(f"Loop 1, {self.send_q.empty()}")
            # First check send_q for new packets to send
            while not self.send_q.empty() and len(inflight_q) < 25:
                # print(f"Loop 2, {self.send_q.empty()}")
                new_packet: InflightPacket = self.send_q.get()
                new_packet.start_time = time.time()
                self.socket.sendto(new_packet.packet,
                                   (self.dst_ip, self.dst_port))
                inflight_q.append(new_packet)

            # Check acks for inflight packets
            # iterate through inflight packets, remove those <= acked_n
            # for the packet (acked_n + 1), check timer
            idx = -1
            resend_all = False
            for i, pack in enumerate(inflight_q):
                if pack.seq_n <= self.acked_n:
                    # packet has been acked, update idx to be removed
                    idx = i
                else:
                    # earliest packet not acked, check timer
                    if (time.time() - pack.start_time) > pack.timeout_s:
                        # print(f"seq_n {pack.seq_n} timed out")
                        resend_all = True

                    break

            if idx > 0:
                inflight_q = inflight_q[idx:]

            if resend_all:
                # if first packet after previous ack'ed timed out,
                # all packets after have also timed out. Resend all
                for pack in inflight_q:
                    pack.start_time = time.time()
                    self.socket.sendto(pack.packet,
                                       (self.dst_ip, self.dst_port))

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        while not self.closed:
            packet = self.recv_q.get()
            print(f"Recv'ed seq_n = {packet.seq_n}")

            if packet.seq_n == self.recv_expect_seq_n:
                self.recv_expect_seq_n += 1

                # send ack
                send_buf = Packet(seq_n=packet.seq_n,
                                  recv_buf_size=0,
                                  ack=True).to_bytes()
                self.socket.sendto(send_buf, (self.dst_ip, self.dst_port))

                return packet.payload

            if packet.seq_n < self.recv_expect_seq_n:
                # This packet was received twice, ignore
                continue
            else:
                self.recv_q.put(packet)

    def _listener(self):
        """Listener running in a background thread"""
        while not self.closed:
            print("Listener loop")
            try:
                buf, addr = self.socket.recvfrom()
                if not buf:
                    continue

                try:
                    packet = Packet.from_bytes(buf)
                except PacketCorruptError:
                    # Ignore corrupt packets and wait for a resend due to timeout
                    # print("Corruption detected")
                    continue

                if packet.ack:
                    self.acked_n = packet.seq_n
                    if packet.fin:
                        self.should_close = True
                    continue

                elif packet.fin:
                    # send fin ack
                    send_buf = Packet(seq_n=packet.seq_n,
                                      recv_buf_size=0,
                                      ack=True,
                                      fin=True).to_bytes()
                    self.socket.sendto(send_buf, (self.dst_ip, self.dst_port))
                    self.should_close = True
                    continue

                self.recv_q.put(packet)

            except Exception as e:
                print("Listener died!")
                print(e)

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
        the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        if not self.should_close:
            # print(f"close called, queue size: {self.buf_q.qsize()}")
            fin_pack = Packet(seq_n=self.send_next_seq_n, fin=True)
            # Sends and waits for FIN ACK
            self.send(fin_pack.to_bytes(), timeout_s=2)
            # Send ACK
            ack_pack = Packet(seq_n=self.send_next_seq_n, ack=True)
            self.socket.sendto(ack_pack.to_bytes(),
                               (self.dst_ip, self.dst_port))

        self.closed = True
        self.socket.stoprecv()
Example #9
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        #self.lock = Lock()
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port
        self.closed = False
        #Buffers and Readers
        self.recvBuffer = []
        self.sendBuffer = []
        self.sCurrSeq = 0
        self.rCurrSeq = 0
        self.retData = b""
        self.ack = False
        self.thread = ThreadPoolExecutor(max_workers=1)
        self.thread.submit(self.listener)
        #Retransmit Trackers
        self.wOn = 0
        self.EarliestAck = 0
        self.sTimer = None
        self.ackTimer = 0
        #TeardownStuff
        self.tDownAck = False
        self.tDownReq = False
        self.tDownTimer = 0
        self.tDownForceTimer = None

    def sortHelp(self, tuple):
        return tuple[0]

    def calcCSum(self, packet):
        m = md5()
        m.update(packet)
        checksum = m.digest()
        checksum = checksum[0:8]
        #print(checksum)
        return checksum

    def sRetransmit(self):
        print("Retransmitting...")
        #print(self.sendBuffer)
        if (self.sendBuffer and not self.closed):
            for rPack in self.sendBuffer:
                self.socket.sendto(rPack, (self.dst_ip, self.dst_port))
            self.sTimer = Timer(0.25, self.sRetransmit)
            self.sTimer.start()

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!
        dataLeft = data_bytes
        datasize = len(data_bytes)
        while len(dataLeft) > 0:
            toSend = min(len(dataLeft), 1450)
            sendNow = dataLeft[0:toSend]
            packet = pack("ll??", self.sCurrSeq, datasize, False, False)
            chSend = packet + sendNow
            checksum = self.calcCSum(chSend)
            fpacket = packet + checksum + sendNow
            self.socket.sendto(fpacket, (self.dst_ip, self.dst_port))
            dataLeft = dataLeft[toSend:]
            self.sendBuffer.append(fpacket)
            self.sCurrSeq += 1
            self.ack = False
            if (self.wOn == 0):
                self.EarliestAck = 0
                self.sTimer = Timer(0.25, self.sRetransmit)
                self.sTimer.start()
            self.wOn += 1
            while self.wOn >= 15:
                time.sleep(0.01)

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        # this sample code just calls the recvfrom method on the LossySocket
        retData = b""
        #print(self.rCurrSeq)
        if (len(self.recvBuffer) > 0):
            self.recvBuffer = sorted(self.recvBuffer, key=self.sortHelp)
            extractedFList = []
            #print(self.recvBuffer)
            for i in self.recvBuffer:
                if (i[0][0] == self.rCurrSeq):
                    self.retData += i[2]
                    self.rCurrSeq += 1
                    extractedFList.append(i)
            for z in extractedFList:
                self.recvBuffer.remove(z)
        returned = self.retData
        self.retData = b""
        return returned

    def tDownForce(self):
        print("TearDown Forced")
        self.tDownAck = True

    def listener(self):
        while not self.closed:
            #print("listening...")
            try:
                data, addr = self.socket.recvfrom()
                if (len(data) != 0):
                    pHeader = data[0:10]
                    pPayload = data[10:]
                    packet = (
                        unpack("ll??", pHeader),
                        pPayload[0:8],
                        pPayload[8:],
                    )
                    checksum = 0
                    recChecksu = packet[1]
                    chSend = pack("ll??", packet[0][0], packet[0][1],
                                  packet[0][2], packet[0][3]) + packet[2]
                    print("Header: ", packet[0][0], packet[0][1], packet[0][2],
                          packet[0][3], " Payload: ", packet[2])
                    checksum = self.calcCSum(chSend)
                    if (checksum == recChecksu):
                        if (
                                packet[0][2]
                                and packet[0][0] > self.EarliestAck
                                and not packet[0][3]
                        ):  #Packet is an Ack and is Acknowledging a more recent packet
                            newEarliest = packet[0][0] - 1
                            packetsConfirmed = newEarliest - self.EarliestAck
                            for i in range(packetsConfirmed):
                                self.sendBuffer.pop(0)
                            self.wOn += -packetsConfirmed
                            self.EarliestAck = newEarliest
                            self.sTimer.cancel()
                            self.sTimer = Timer(0.25, self.sRetransmit)
                            self.sTimer.start()
                        elif (packet[0][2] and packet[0][3]
                              and self.tDownReq):  #Packet is a Teardown Ack
                            self.tDownAck = True
                        elif (
                                not packet[0][2] and not packet[0][3]
                        ):  #Packet is neither a teardown req nor an ACK -- its data
                            if (packet[0][0] >= self.rCurrSeq
                                    and packet not in self.recvBuffer
                                ):  #If we don't already have it, save it
                                self.recvBuffer.append(packet)
                            if ((time.perf_counter() - self.ackTimer) > 0.10
                                ):  #If we haven't acked recently, ack it.
                                self.ackTimer = time.perf_counter()
                                header = pack("ll??", self.rCurrSeq, 10, True,
                                              False)
                                chSend = header + b"  "
                                checksum = self.calcCSum(chSend)
                                fpacket = header + checksum + b"  "
                                self.socket.sendto(
                                    fpacket, (self.dst_ip, self.dst_port))
                        elif (
                                packet[0][3] and self.tDownReq
                        ):  #Packet is a Teardown Request and we are also in the process of tearing down; Send a Teardown Ack
                            header = pack("ll??", self.rCurrSeq, 10, True,
                                          True)
                            chSend = header + b"  "
                            checksum = self.calcCSum(chSend)
                            fpacket = header + checksum + b"  "
                            self.socket.sendto(fpacket,
                                               (self.dst_ip, self.dst_port))
                            self.tDownForceTimer = Timer(2.0, self.tDownForce)
                            self.tDownForceTimer.start()

            except Exception as e:
                print("listener died")
                print(e)

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        header = pack("ll??", self.rCurrSeq, 10, False, True)
        chSend = header + b"  "
        checksum = self.calcCSum(chSend)
        fpacket = header + checksum + b"  "
        self.socket.sendto(fpacket, (self.dst_ip, self.dst_port))
        self.tDownReq = True
        self.tDownTimer = time.perf_counter()
        while (not self.tDownAck):
            time.sleep(0.05)
            #print(self.tDownAck)
            now_time = time.perf_counter()
            if (now_time - self.tDownTimer > 0.20):
                print("Retransmitting Teardown...")
                self.socket.sendto(fpacket, (self.dst_ip, self.dst_port))
        self.closed = True
        self.sTimer.cancel()
        self.thread.shutdown()
        print("SUCCESSFULLY CLOSED")
        self.socket.stoprecv()
Example #10
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.send_seq = 0x00000000
        self.send_buffer = dict()

        self.recv_seq = 0x00000000
        self.recv_buffer = dict()

        self.closed = False

        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
        self.executor.submit(self.listener)

        self.acks = set()

        self.nagles_binary_string = b''

    def resend(self, seq):
        self.socket.sendto(self.send_buffer[seq], (self.dst_ip, self.dst_port))
        return Timer(.5, self.repeat, [seq]).start()

    def send(self, data_bytes: bytes) -> None:
        if not len(self.nagles_binary_string):
            Timer(0.05, self.nagles_algorithm, [len(data_bytes)]).start()
            self.nagles_binary_string = data_bytes
        else:
            self.nagles_binary_string += data_bytes

    def nagles_algorithm(self, currData):
        if currData < len(self.nagles_binary_string):
            return Timer(1, self.nagles_algorithm,
                         [len(self.nagles_binary_string)]).start()
        for data in self.partition_data(self.nagles_binary_string):
            self.send_buffer[self.send_seq] = self.build_packet(
                data, 0, self.send_seq, 0)
            self.resend(self.send_seq)
            self.send_seq += 1
        self.nagles_binary_string = b''

    def repeat(self, seq):
        if seq not in self.acks:
            self.resend(seq)

    def send_ack(self, seq):
        #print("Sending Acknowledgement for Packet #" + str(seq))
        self.socket.sendto(self.build_packet(bytes(), 1, seq, 0),
                           (self.dst_ip, self.dst_port))

    def send_fin(self):
        self.socket.sendto(self.build_packet(bytes(), 0, self.send_seq, 1),
                           (self.dst_ip, self.dst_port))

    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        while self.recv_seq not in self.recv_buffer:
            time.sleep(0.01)
        self.recv_seq += 1
        return self.recv_buffer.pop(self.recv_seq - 1)

    def close(self) -> None:
        while self.send_seq not in self.acks:
            self.send_fin()
            time.sleep(0.1)
        print("FIN HANDSHAKE")
        time.sleep(15)
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        self.closed = True
        self.socket.stoprecv()

    def listener(self):
        while not self.closed:
            try:
                packet = self.socket.recvfrom()[0]
                if packet:
                    seq, ack, fin, stored_hash, data = self.deconstruct_packet(
                        packet)

                    comparison_packet = struct.pack(
                        'iii' + str(len(data)) + 's', seq, ack, fin, data)
                    # print('comparison packet seq: ', seq)
                    # print(comparison_packet)
                    hashed_packet_code = self.hash_helper(comparison_packet)

                    hash_check = self.hash_validation(stored_hash,
                                                      hashed_packet_code)
                    if hash_check:
                        continue
                    if ack:
                        self.acks.add(seq)
                    elif fin:
                        self.send_ack(seq)
                    else:
                        self.recv_buffer[seq] = data
                        self.send_ack(seq)

            except Exception as e:
                print("listener died!")
                print(e)

    def partition_data(self, data):
        return (data[0 + i:1444 + i] for i in range(0, len(data), 1444))

    # packs data and hashes it
    def build_packet(self, data, ack, seq, fin):
        first_packet = struct.pack('iii' + str(len(data)) + 's', seq, ack, fin,
                                   data)
        # print('first packet for seq', seq)
        # print(first_packet)
        hashed_packet_code = self.hash_helper(first_packet)
        return struct.pack('iii16s' + str(len(data)) + 's', seq, ack, fin,
                           hashed_packet_code, data)

    def deconstruct_packet(self, data):
        return struct.unpack('iii16s' + str(len(data) - 28) + 's', data)

    def hash_validation(self, stored_hash, hashed_packet_code):
        # hashed_code = self.hash_helper(data)
        if (stored_hash == hashed_packet_code): return False
        return True

    # returns a hash value
    def hash_helper(self, data):
        m = hashlib.md5()
        m.update(data)
        return m.digest()
Example #11
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port
        self.data = b''
        self.seqnum = 0  # sending seq num
        self.recvnum = 1  # receiver's current receiving number in order
        self.buffer = {}  # receiving buffer
        self.ack = []  # all the ACKs we've received
        self.TIMER_THREAD = {}
        self.MD5 = {}
        # self.NagleBuffer = b''
        # self.fin = 0  # initialize the fin signal
        # self.finACK = 0  # initialize the ACK of fin
        self.flag = 1
        self.nagleEnd = 0
        # self.secfin = 0
        self.retranHeader = {}
        self.executor = ThreadPoolExecutor(max_workers=2)
        self.executor.submit(self.listener)
        self.executor.submit(self.nagle)

    def nagle(self) -> None:
        # print("a")
        while self.nagleEnd == 0:
            # print("b")
            temp = self.data
            # print("self.data",self.data)
            time.sleep(0.25)
            if temp == self.data and temp != b'':
                self.nagleEnd = 1
                self.send(temp)
                # print("c")
                break

    def listener(self) -> None:
        # print(self.flag)
        while self.flag == 1:
            # print("a")
            ss, addr = self.socket.recvfrom()
            # print("b")
            if addr == ("", 0):
                # print(self.flag)
                # print("c")
                break
            if len(ss) <= 34:  # if receive ACK, STOP corresponding TIMER
                # print("e")
                cmd = "!H32s"
                # print("收到ss:",ss)
                this_ack, ackchsm = struct.unpack(cmd, ss)
                # print("收到this_ack:",this_ack)
                rcv_csm = self.getchecksum(str(this_ack).encode()).encode()
                # print("计算rcv_csm:",rcv_csm)
                # print("收到ackhsm:",ackchsm)
                if rcv_csm == ackchsm:
                    # print("收到ACK chsm: == ")
                    self.ack.append(this_ack)
                    # print("ack:", self.ack)
                    if this_ack == 0:
                        self.finACK = 1
                    # print("f")
                else:
                    continue
                    # print("不等rec:",rcv_csm," & ",ackchsm)

            else:  # if receive DATA, put into buffer
                # print("g")
                cmd = "!H32s" + str(len(ss) - 34) + "s"
                header_sq, chsm, data = struct.unpack(cmd, ss)
                # print("receive data header",header_sq)
                # print("receive data :",data)
                # print("Got header is %d" % header)

                # if checksum is wrong, drop the packet, resend
                databody = str(header_sq).encode() + data
                recv_chsm = self.getchecksum(databody).encode()
                # print("databody",databody)
                # print("chsm:",chsm)
                # print("recv_chsm",recv_chsm)

                if recv_chsm != chsm:
                    # print("recv_chsm != chsm")
                    continue
                if recv_chsm == chsm:
                    # print("h")
                    # print("recv_chsm == chsm")
                    if header_sq < self.recvnum:
                        # print("header<recvnum, recvnum is ", self.recvnum)
                        ack = struct.pack("!H32s", header_sq, self.getchecksum(str(header_sq).encode()).encode()) #--ack = struct.pack("!H", header_sq)
                        # print("多余ack",header_sq)
                        # self.socket.sendto(ack, (self.dst_ip, self.dst_port))
                    else:

                        self.buffer.update({header_sq: data})
                        # print("buffer.update::::", self.buffer)
                        # print("buffer update header:",header_sq)
                        ack = struct.pack("!H32s", header_sq, self.getchecksum(str(header_sq).encode()).encode()) #ack = struct.pack("!H", header_sq)
                        # print("buffer:",self.buffer)
                    # print("发送ack", header_sq)
                    self.socket.sendto(ack, (self.dst_ip, self.dst_port))
                    # print("i")
                    # print("--Sent ACK:", header_sq)

                else:
                    # print("ERROR checksum, dropped the packet..")
                    # print("##:", header_sq)

                    continue  # ignore this packet

    def retransmission(self, ss: struct) -> None:
        # resend ss after exceeding time
        # self.socket.sendto(ss, (self.dst_ip, self.dst_port))
        # deparse the ss to get the header
        time.sleep(0.25)
        cmd = "!H32s" + str(len(ss) - 34) + "s"
        header, _, dt = struct.unpack(cmd, ss)

        if header not in self.ack:
            # print("重发", header)
            self.socket.sendto(ss, (self.dst_ip, self.dst_port))
            time.sleep(0.5)
            self.retransmission(ss)
            # t2 = Timer(0.5, self.retransmission, (ss,))  # self.seqnum,
            # if dt != str("\r\n").encode():
            #     self.TIMER_THREAD.update({header: t2})
            # t2.start()
        else:
            self.ack.remove(header)
            # print("丢:",header)
            # if dt == str("\r\n").encode():
            #     if self.finACK != 1:
            #         time.sleep(1)
            #         self.retransmission(ss)
            # else:

            self.TIMER_THREAD.pop(header)

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        raw_data = data_bytes
        # self.data += raw_data
        if len(self.data) + len(raw_data) <= 1438 and self.nagleEnd == 0:
            self.data += raw_data
            return None
        if self.nagleEnd == 1:
            self.data = raw_data
        # if self.data == b'':
        #     self.data = raw_data
        # print("all data:", self.data)

        while True:

            # if len(raw_data) > 1438:
            if len(self.data) > 1438:
                # packet_data_bytes = raw_data[0:1438]  # !python note: range needs to cover the higher index
                packet_data_bytes = self.data[0:1438]
                # raw_data = raw_data[1438:]
                raw_data = self.data[1438:]
                self.seqnum += 1

                # warp header+data, combine seqNum & checksum
                databody = str(self.seqnum).encode() + packet_data_bytes

                chks = self.getchecksum(databody).encode()
                # print("CHECKSUM LEN: ", len(chks))
                ss = struct.pack("!H32s1438s", self.seqnum, chks, packet_data_bytes)

                self.socket.sendto(ss, (self.dst_ip, self.dst_port))
                t_1 = Timer(0.5, self.retransmission, (ss,))
                self.TIMER_THREAD.update({self.seqnum: t_1})
                t_1.start()
                time.sleep(0.1)

            else:

                # if len(raw_data) != 0:
                if len(self.data) != 0:
                    self.seqnum += 1
                    # self.retranHeader.update({self.seqnum:1})
                    # cmd = "!H32s" + str(len(raw_data)) + "s"
                    cmd = "!H32s" + str(len(self.data)) + "s"
                    # databody = str(self.seqnum).encode() + raw_data
                    databody = str(self.seqnum).encode() + self.data
                    chks = self.getchecksum(databody).encode()
                    # ss = struct.pack(cmd, self.seqnum, chks, raw_data)
                    ss = struct.pack(cmd, self.seqnum, chks, self.data)
                    self.socket.sendto(ss, (self.dst_ip, self.dst_port))
                    t = Timer(0.5, self.retransmission, (ss,))
                    if raw_data != str("\r\n").encode():
                        self.TIMER_THREAD.update({self.seqnum: t})
                    t.start()
                    time.sleep(0.1)
                    # raw_data = b''
            if len(raw_data) > 1438 and self.data == b'':
                # print("111")
                self.data = raw_data
                continue
            self.data = raw_data
            raw_data = b''
            # print("remain",self.data)
            if self.nagleEnd == 1:
                self.data = b''
            if len(self.data) == 0:
                # print("send end")
                break


    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        rs = ''

        while True:
            if self.buffer == {}:
                continue

            m = max(self.buffer.keys())
            for i in range(self.recvnum, m+1):
                if self.recvnum in self.buffer.keys():
                    rs = rs + self.buffer.pop(self.recvnum).decode()
                    self.recvnum += 1

            if rs == '':
                continue

            break

        return rs.encode()

    def getchecksum(self, databody):
        md5 = hashlib.md5()
        md5.update(databody)
        chks = md5.hexdigest()
        # self.MD5.update({seq:chks})
        return chks

    # def SecondClose(self):
    #     if self.finACK != 1 or self.fin != 1:
    #         self.sendFin()
    #
    # def sendFin(self):
    #     while True:
    #         if len(self.TIMER_THREAD.keys()) == 0:
    #             self.ack.append(self.seqnum + 1)
    #             self.send(str("\r\n").encode())
    #             break

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        while True:
            # print("self.buffer",self.buffer)
            if len(self.buffer) == 0:
                time.sleep(5)
                # print("self.buffer", self.buffer)
                if len(self.buffer) == 0:
                    self.socket.stoprecv()
                    self.flag = 0
                    self.nagleEnd = 1
                    break
Example #12
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port
        self.expected_num = 0
        self.rec_buf = {}
        self.timer_buf = {}
        self.ack_buf = []
        self.listener = True
        self.timeout = 1
        self.other_fin = False
        executor = ThreadPoolExecutor(max_workers=3)
        executor.submit(self.listening)
        executor.submit(self.acking)

    def listening(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        while self.listener:
            data, addr = self.socket.recvfrom()
            if data and data[0] == 65:
                self.ack_recv(data)
            elif data and data[0] == 70:
                self.other_fin = True
            elif data:
                seq_num = get_seq_num(data)
                self.rec_buf[seq_num] = data
                # self.ack_send(data)
                self.ack_buf.append(data)
            print('are we still listening?')
        print('something broke us out of the loop!')

    def acking(self):
        while self.listener:
            while self.ack_buf:
                self.ack_send(self.ack_buf[0])
                self.ack_buf.pop(0)

    def send(self, data_bytes: bytes):
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!
        # length check should be dif bc must make space for header
        header = str(self.expected_num).encode('utf-8')+b'\r\n\r\n'
        if len(data_bytes) + len(header) > 1472:
            payload = data_bytes[:1472-len(header)]
            self.socket.sendto(header+payload, (self.dst_ip, self.dst_port))
            self.expected_num += 1472
            timer = Timer(self.timeout, self.retransmission, [header+payload])
           
            
            # expect_num already includes the new bytes making it an ACk
            self.timer_buf[self.expected_num] = timer
            # self.timer_buf[self.expected_num+len(payload)] = timer
            timer.start()
            print('Set timer in send with ack num: ', self.expected_num)
            self.send(data_bytes[1472-len(header):])
        else:
            self.socket.sendto(header+data_bytes, (self.dst_ip, self.dst_port))
            self.expected_num += len(header)+len(data_bytes)
            timer = Timer(self.timeout, self.retransmission, [header+data_bytes])
            
            #Expected_num already includes the bytes making it an ack
            self.timer_buf[self.expected_num] = timer
            # self.timer_buf[self.expected_num+len(data_bytes)] = timer

            print('Set timer in send with ack num: ', self.expected_num)
            timer.start()

    def recv(self) -> bytes:
        # if not expected data, return nothing
        if self.expected_num not in self.rec_buf:
            return b''

        # if expected data, check buffer for contiguous segments and return total contiguous payload
        application_data = self.rec_buf[self.expected_num]
        self.rec_buf.pop(self.expected_num)
        self.expected_num += len(application_data)
        application_data = get_payload(application_data)
        while self.expected_num in self.rec_buf:
            data = self.rec_buf[self.expected_num]
            self.rec_buf.pop(self.expected_num)
            self.expected_num += len(data)
            application_data += get_payload(data)
        return application_data

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        print('Ready to close Timer buf: ' + str(self.timer_buf))
        while len(self.timer_buf) != 0:
            time.sleep(5)
            print('Waiting Timer buf: ' + str(self.timer_buf))
            pass
        print('Closed Timer buf: ' + str(self.timer_buf))
        
        self.send_fin()
        print('Sent the fin')
        while not self.other_fin:
            time.sleep(5)
            print('Waiting for the other to finish')
            print('Sent the fin again just in case ~shrug~')
            self.send_fin()
            pass
        print('And we have received word the other has finished')
        self.listener = False

    def send_fin(self):
        fin = b'F\r\n\r\n'
        self.socket.sendto(fin, (self.dst_ip, self.dst_port))
    
    def ack_recv(self, data: bytes):
        ack_num = get_ack_num(data)
        print('Received Ack_num: ' + str(ack_num))
        self.timer_buf[ack_num].cancel()
        self.timer_buf.pop(ack_num)


    def ack_send(self, data: bytes):
        ack_num = str(get_seq_num(data)+len(data))
        print('Sent Ack_num: ' + str(ack_num))
        header = b'A'+ack_num.encode('utf-8')+b'\r\n\r\n'
        self.socket.sendto(header, (self.dst_ip, self.dst_port))

    def retransmission(self, data: bytes):
        print('THE TIMER HAS RUN OUT with ACK number: ', get_seq_num(data)+len(data))
        self.socket.sendto(data, (self.dst_ip, self.dst_port))
        timer = Timer(self.timeout, self.retransmission, [data])
        self.timer_buf[get_seq_num(data)+len(data)] = timer
        # self.timer_buf[get_seq_num(data)] = timer
        print("set timer in retransmit with ack num: ", get_seq_num(data)+len(data))
        timer.start()
Example #13
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port
        self.expected_num = 0
        self.rec_buf = {}
        self.timer = None
        self.timer_ack = -1
        self.ack_buf = []
        self.listener = True
        self.timeout = .25
        self.unacked = {}
        self.other_fin = False
        self.own_fin = False
        executor = ThreadPoolExecutor(max_workers=3)
        self.listening_future = executor.submit(self.listening)
        self.acking_future = executor.submit(self.acking)

    def listening(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        while self.listener:
            data, addr = self.socket.recvfrom()
            checksum = get_checksum(data)
            if data:
                calculated_checksum = self.calculate_checksum(data[4:])
            if data and checksum == calculated_checksum:
                if data and data[4] == 65:
                    self.ack_recv(data)
                elif data and data[4] == 70:
                    self.other_fin = True
                elif data:
                    seq_num = get_ack_or_seq_num(data)
                    if seq_num != -1:
                        self.rec_buf[seq_num] = data
                        self.ack_buf.append(data)

    def acking(self):
        while self.listener:
            while self.ack_buf:
                self.ack_send(self.ack_buf[0])
                self.ack_buf.pop(0)
                #print('updated ack_buf: ' + str(self.ack_buf))

    def send(self, data_bytes: bytes):
        """Note that data_bytes can be larger than one packet."""
        # length check should be dif bc must make space for header
        seq_num = str(self.expected_num).encode('utf-8')

        # checksum = 4 bytes,
        # flag first, then checksum, then seq/ack number, then \r\n\r\n, then payload
        if 4+1+len(data_bytes) + len(seq_num) + len(b'\r\n\r\n') > 1472:
            payload = data_bytes[:1472-4-1-len(seq_num)-len(b'\r\n\r\n')]
            checksum = self.calculate_checksum(
                b'S'+seq_num+b'\r\n\r\n'+payload)
            header = checksum + b'S' + \
                seq_num+b'\r\n\r\n'

            # not_checksum = seq_num+b'\r\n\r\n'
            # checksum = self.calculate_checksum(payload).encode('utf-8')
            # header = seq_num+b'C'+checksum+b'\r\n\r\n'
            self.socket.sendto(header+payload, (self.dst_ip, self.dst_port))
            self.expected_num += len(header+payload)
            self.unacked[self.expected_num] = header+payload
            if len(self.unacked) == 1:
                self.timer = Timer(
                    self.timeout, self.retransmission, [header+payload])
                self.timer.start()
                self.timer_ack = self.expected_num
            self.send(data_bytes[1472-len(header):])
        else:
            checksum = self.calculate_checksum(
                b'S'+seq_num+b'\r\n\r\n'+data_bytes)
            message = checksum+b'S'+seq_num+b'\r\n\r\n'+data_bytes
            self.socket.sendto(message, (self.dst_ip, self.dst_port))
            self.expected_num += len(message)
            self.unacked[self.expected_num] = message
            if len(self.unacked) == 1:
                self.timer = Timer(
                    self.timeout, self.retransmission, [message])
                self.timer.start()
                self.timer_ack = self.expected_num

    def recv(self) -> bytes:
        # if not expected data, return nothing
        if self.expected_num not in self.rec_buf:
            return b''

        # if expected data, check buffer for contiguous segments and return total contiguous payload
        application_data = self.rec_buf[self.expected_num]
        self.rec_buf.pop(self.expected_num)
        self.expected_num += len(application_data)
        application_data = get_payload(application_data)
        while self.expected_num in self.rec_buf:
            data = self.rec_buf[self.expected_num]
            self.rec_buf.pop(self.expected_num)
            self.expected_num += len(data)
            application_data += get_payload(data)
        return application_data

    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions
           your code goes here, especially after you add ACKs and retransmissions."""
        print('close initiated')
        while len(self.unacked) != 0:
            pass
        print('no more unacked')
        self.send_fin()
        self.own_fin = True
        while not self.other_fin:
            self.send_fin()
        print('other one has no acks')
        while(threading.active_count() > 4):
            pass
        #print('threading count less than 4')
        self.socket.stoprecv()
        self.listener = False

    def send_fin(self):
        fin = b'F\r\n\r\n'
        checksum = self.calculate_checksum(fin)
        self.socket.sendto(checksum+fin, (self.dst_ip, self.dst_port))

    def ack_recv(self, data: bytes):
        ack_num = get_ack_or_seq_num(data)
        if ack_num != -1 and ack_num in self.unacked:
            self.unacked.pop(ack_num)
        if ack_num and ack_num == self.timer_ack:
            self.timer.cancel()
            if self.unacked:
                new_timer_ack = sorted(self.unacked.keys())[0]
                self.timer_ack = new_timer_ack
                self.timer = Timer(
                    self.timeout, self.retransmission, [self.unacked[new_timer_ack]])
                self.timer.start()
            else:
                self.timer = None
                self.timer_ack = -1

    def ack_send(self, data: bytes):
        ack_num = str(get_ack_or_seq_num(data)+len(data))
        rest = b'A'+ack_num.encode('utf-8')+b'\r\n\r\n'
        checksum = self.calculate_checksum(rest)
        self.socket.sendto(checksum+rest, (self.dst_ip, self.dst_port))

    def retransmission(self, data: bytes):
        if not (self.other_fin and self.own_fin):
            self.socket.sendto(data, (self.dst_ip, self.dst_port))
            self.timer = Timer(self.timeout, self.retransmission, [data])
            self.timer.start()

    def calculate_checksum(self, msg: bytes) -> bytes:
        try:
            msg = msg.decode('utf-8')
            s = 0       # Binary Sum
            # loop taking 2 characters at a time
            for i in range(0, len(msg), 2):
                if (i+1) < len(msg):
                    a = ord(msg[i])
                    b = ord(msg[i+1])
                    s = s + (a+(b << 8))
                elif (i+1) == len(msg):
                    s += ord(msg[i])
                else:
                    raise "Something Wrong here"
            s = s + (s >> 16)
            s = ~s & 0xffff
            s = str(hex(s)[2:])
            while len(s) < 4:
                s = "0"+s
            return s.encode('utf-8')
        except:
            return b''
Example #14
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        #sequence numbers
        self.seq_num = 0
        self.recv_num = 0

        #send & receive buffers
        #self.s_buff = {}
        self.r_buff = {}

    def send(self, data_bytes: bytes) -> None:
        """Note that data_bytes can be larger than one packet."""
        # Your code goes here!  The code below should be changed!

        while len(data_bytes) > 1468:
            header = pack('i', self.seq_num)
            sbytes = header + data_bytes[0:1468]
            self.socket.sendto(sbytes, (self.dst_ip, self.dst_port))
            data_bytes = data_bytes[1468:]

            self.seq_num = self.seq_num + 1

            #self.s_buff[self.seq_num] = sbytes

        header = pack('i', self.seq_num)
        sbytes = header + data_bytes
        self.socket.sendto(sbytes, (self.dst_ip, self.dst_port))
        self.seq_num = self.seq_num + 1


    def recv(self) -> bytes:
        """Blocks (waits) if no data is ready to be read from the connection."""
        # your code goes here!  The code below should be changed!
        while True:

            #can be while loop too
            if self.recv_num in self.r_buff:

                data = self.r_buff[self.recv_num]
                # dont technically need this
                # del self.r_buff[self.recv_num]
                self.recv_num += 1

                return data

            data, addr = self.socket.recvfrom(1472)
            recv_header = unpack('i', data[0:4])[0]
            data = data[4:]
            self.r_buff[recv_header] = data


    def close(self) -> None:
        """Cleans up. It should block (wait) until the Streamer is done with all
           the necessary ACKs and retransmissions"""
        # your code goes here, especially after you add ACKs and retransmissions.
        pass
Example #15
0
class Streamer:
    def __init__(self, dst_ip, dst_port, src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""

        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        self.recv_buff = {}
        self.recv_seq_num = 0
        self.send_seq_num = 0

        self.closed = False
        self.ack = False
        self.recv_fin = False
        self.recv_finack = False

        executor = concurrent.futures.ThreadPoolExecutor(max_workers=2)
        executor.submit(self.listener)

    def listener(self):
        while not self.closed:
            try:
                data, addr = self.socket.recvfrom()
                if len(data) == 0:
                    continue

                recv_packet_num = struct.unpack('i', data[0:4])[0]
                is_ack = struct.unpack('i', data[4:8])[0]
                is_fin = struct.unpack('i', data[8:12])[0]
                data_bytes = data[12:]

                if len(data_bytes) > 0:
                    is_ack = 0
                    is_fin = 0

                if is_fin:
                    # Case1: A FINACK packet is received
                    if is_ack:
                        print('FINACK received.')
                        self.recv_finack = True

                    # Case2: A FIN packet is received - We FINACK the FIN
                    else:
                        print('FIN received.')
                        self.recv_fin = True

                        print('sending FINACK...')
                        self.send(b'', 1, 1)

                else:
                    # Case3: An ACK packet is received - We notify the main thread that an ACK was received
                    if is_ack == 1:
                        print('ACK received for packet ', recv_packet_num)
                        self.ack = True

                    # Case4: The received packet is normal data that we must add to the recv buffer
                    else:
                        data_bytes = data[12:]

                        # Add the data to the receive buffer
                        self.recv_buff[recv_packet_num] = data_bytes

                        # Send ACK packet
                        print('ACK sent for packet ', self.recv_seq_num)
                        self.socket.sendto(
                            struct.pack('iii', recv_packet_num, 1, 0),
                            (self.dst_ip, self.dst_port))

            except Exception as e:
                print("listener died!")
                print(e)

    def send(self, data_bytes: bytes, is_ack=0, is_fin=0) -> None:
        if is_ack or is_fin:
            self.socket.sendto(
                struct.pack('iii', self.recv_seq_num, is_ack, is_fin),
                (self.dst_ip, self.dst_port))
            print('ack number: ', self.recv_seq_num)
            return

        # Break up the data into chunks of 1460 bytes and send it
        while len(data_bytes) > 1460:
            header = struct.pack('iii', self.send_seq_num, is_ack, is_fin)

            packet = header + data_bytes[0:1460]

            self.socket.sendto(packet, (self.dst_ip, self.dst_port))
            self.ack = False
            time.sleep(0.15)

            # Waiting to receive an ACK for the sent data before the send is "completed"
            while not self.ack:
                time.sleep(0.25)

                if not self.ack:
                    self.socket.sendto(packet, (self.dst_ip, self.dst_port))
                else:
                    break

            data_bytes = data_bytes[1460:]
            self.send_seq_num = self.send_seq_num + 1

        # Sending the last packet of data of len <= 1464 bytes
        header = struct.pack('iii', self.send_seq_num, is_ack, is_fin)
        packet = header + data_bytes
        self.socket.sendto(packet, (self.dst_ip, self.dst_port))
        self.send_seq_num = self.send_seq_num + 1
        self.ack = False

        time.sleep(0.25)

        # Waiting to receive an ACK for the sent data before the send is "completed"
        while not self.ack:
            time.sleep(0.25)

            if not self.ack:
                self.socket.sendto(packet, (self.dst_ip, self.dst_port))
            else:
                break

    def recv(self) -> bytes:
        while True:
            if self.recv_seq_num in self.recv_buff:
                data_bytes = self.recv_buff[self.recv_seq_num]
                self.recv_buff.pop(self.recv_seq_num)
                self.recv_seq_num = self.recv_seq_num + 1
                return data_bytes

    def close(self) -> None:
        print('sending FIN...')
        self.send(b'', 0, 1)

        while not self.recv_finack:
            time.sleep(0.1)

            if not self.recv_finack:
                self.send(b'', 0, 1)
            else:
                break

        while not self.recv_fin:
            print('waiting to receive a FIN...')
            time.sleep(0.1)

        time.sleep(2)
        print('Closing...')
        self.closed = True
        self.socket.stoprecv()
        return
Example #16
0
class Streamer:
    def __init__(self, dst_ip, dst_port,
                 src_ip=INADDR_ANY, src_port=0):
        """Default values listen on all network interfaces, chooses a random source port,
           and does not introduce any simulated packet loss."""
        self.socket = LossyUDP()
        self.socket.bind((src_ip, src_port))
        self.dst_ip = dst_ip
        self.dst_port = dst_port

        # Parameters for managing order
        self.current_receiving_SEQ = 0
        self.packing_seq = 0
        self.buffer = {}

        # Thread management
        self.closed = False
        self.executor = concurrent.futures.ThreadPoolExecutor(max_workers=1)
        self.listen_thread = self.executor.submit(self.listener)

        # ACK management
        self.ACK = {}

        # FIN handshake 
        self.FIN = False # has the other party sent the fin message yet?

        # Pipelining
        self.sending_buffer = {}

        # Extra Credit 
        self.all_data = b""
        self.first_time = True



    def send(self, data_bytes: bytes) -> None:
        if self.first_time:
            Timer(0.01, self.__data_check, [len(data_bytes)]).start()
            self.first_time = False
        self.all_data += data_bytes
       
    def recv(self) -> bytes:    
        # If the desired packet is already in the buffer, return it. Othwerise, wait.
        while self.current_receiving_SEQ not in self.buffer:
            time.sleep(0.01)
        self.current_receiving_SEQ += 1
        return self.buffer.pop(self.current_receiving_SEQ-1)

    def close(self) -> None:
        self.__FIN_handshake()
        self.closed = True
        self.socket.stoprecv()
        while not self.listen_thread.done():
            time.sleep(0.01)
        self.executor.shutdown()

    def listener(self):
        while not self.closed: 
            try:
                data = self.socket.recvfrom()[0]
                self.__packet_handler(data)
            except Exception as e:
                print("listener died!")
                print(e)
        return 

#### HELPERS #####

    #### Sending helpers
    # Breaks data up into pieces no largers than 's'
    # and returns a list of broken up pieces 
    def __byte_breaker(self, b: bytes, s: int):
        return [b[i:i+s] for i in range(0, len(b), s)]
    
    # Sends data and starts a timer 
    def __recursive_send(self, seq):
        to_send = self.sending_buffer[seq]
        self.socket.sendto(to_send, (self.dst_ip, self.dst_port))
        Timer(1, self.__selective_repeat, [seq]).start()
        return

    # Checks if ACK is received. Calls __recursive_send if not.
    def __selective_repeat(self, seq):
        if seq not in self.ACK:
            return self.__recursive_send(seq)      


    #### Nagles Algorithm Helpers (EC) 
    # Checks if there has been more data added to sending queue. If not, sends, otherwise
    # it starts a timer to check again in 1 ms.
    def __data_check(self, num):
        if len(self.all_data) > num:
            return Timer(0.01, self.__data_check, [len(self.all_data)]).start()
        return self.__send_data()

    # Takes the whole sending queue and sends it out with __recursive send for 
    # guaranteed delivery.
    def __send_data(self):
        byte_ls = self.__byte_breaker(self.all_data, 1456)
        for data in byte_ls:
            to_send = self.__packer(self.packing_seq, data, ack=False)
            self.sending_buffer[self.packing_seq] = to_send
            self.__recursive_send(self.packing_seq)
            self.packing_seq += 1
        self.first_time = True
        self.all_data = b''


    #### Helpers for packing and unpacking data with structs
    # Packs a sequence number, data, ACK flag, and hash into a struct
    def __packer(self, seq, data, ack=False) -> struct:
        f = f'h {len(data)}s b'
        insecure_struct = struct.pack(f, seq, data, ack)
        secure_struct = self.__hash_pack(insecure_struct)
        return secure_struct

    # Unpacks a struct w/ a hash, sequence number, data, and ACK 
    def __unpacker(self, packed) -> tuple:
        f = f'i h {len(packed)-7}s b'
        return struct.unpack(f, packed)

    #### Flow Control Helpers
    # General function that deals with received packets. Sends things 
    # where they need to go depending on the type/state of the packet
    def __packet_handler(self, _data):
        if not _data:
            return
        seq, seg, ack = self.__unpacker(_data)[1:]
        if not self.__hash_check(_data):
            return 
        if ack:
            self.ACK[seq] = True   
        elif seq == -1:
            self.FIN = True
            self.__send_ACK(seq)
        else:
            self.buffer[seq] = seg
            self.__send_ACK(seq)

    # Sends an ACK for a given seq number
    def __send_ACK(self, seq):
        f = 'h s b'
        s = struct.pack(f, seq, b'a', True)
        s = self.__hash_pack(s)
        self.socket.sendto(s, (self.dst_ip, self.dst_port))

 
    #### Helpers for the FIN handshake
    # Recursively sends a FIN message to indicate that we're done sending
    def __send_fin(self) -> None:
        FIN = struct.pack('h 4s b', -1, b'done', False)
        FIN = self.__hash_pack(FIN)
        self.socket.sendto(FIN, (self.dst_ip, self.dst_port))
        self.__wait_fin_ACK()

    # Waits until an ACK for the FIN is received. If it's not recieved 
    # soon enough, it resends the FIN.
    def __wait_fin_ACK(self):
        count = 0
        while -1 not in self.ACK:
            time.sleep(0.01)
            count += 1
            if count > 25:
                self.__send_fin()

    # 1) Sends a message to indicate we're done sending data
    # 2) Waits until we receive message 
    # 3) After receiving the FIN, we wait two seconds to make sure our FIN ACK
    #    doesn't get lost
    def __FIN_handshake(self):
        self.__send_fin()
        while not self.FIN:
            time.sleep(0.01)
        time.sleep(3)
        

    #### Helpers for creating and verifying hashes 
    # Given a packed struct, adds a hash to it
    def __hash_pack(self, _struct):
        f = f'i {len(_struct)}s'
        code = adler32(_struct) % 2147483647 # keeps the result a 32 bit integer
        return struct.pack(f, code, _struct)

    # Given a packed struct, determines if the hash is valid
    def __hash_check(self, _data):
        expected = self.__unpacker(_data)[0]
        actual = adler32(_data[4:]) % 2147483647 
        return expected == actual