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 __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 __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
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
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 __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 __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)
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
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 __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)
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()
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 __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 __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 __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 __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
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
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))
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()
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
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()
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)
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
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
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: 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()
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''
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()
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