def mc_send_file(hostip, mcgrpip, mcport_send, mcport_recv, filename): datasize = os.stat(filename).st_size print(f'Sending file: {filename}\n') if datasize > (common.MAX_SEQ * common.BLOCKSIZE): print('WARNING: data size exceeds 1-petabyte, transmitted data will be'\ + ' truncated!', file=sys.stderr) # 1. Get a socket to send data with and a socket to receive data with endpoint = (mcgrpip, mcport_send) sender = common.prep_sender(hostip) receiver = common.prep_receiver(hostip, mcgrpip, mcport_recv) # 2. Send metadata send_metadata(sender, endpoint, receiver, filename, datasize) # 3. Read the file and the transmit the file content in packets of BLOCKSIZE # data length; the single-byte prefix for file content is CONTENT_OP with open(filename, 'rb') as f: seq_num = 1 more = '0' packets_left = count_blocks(common.BLOCKSIZE, datasize, filename) content_bytes = f.read(common.BLOCKSIZE) while content_bytes and seq_num <= common.MAX_SEQ: content_sent = False more = '1' if packets_left > 1 else '0' packet = common.CONTENT_OP.encode() \ + str(seq_num).zfill(common.SEQ_NUM_LEN).encode() \ + more.encode() \ + content_bytes while not content_sent: sender.sendto(packet, endpoint) feed_packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if not timedout and opcode == common.FEEDBACK_OP and \ feed_packet[1:common.SEQ_NUM_LEN + 1] == \ str(seq_num).zfill(common.SEQ_NUM_LEN): content_sent = True seq_num += 1 packets_left -= 1 content_bytes = f.read(common.BLOCKSIZE) time.sleep(common.PACKET_DELAY) # 4. Handle final feedback and release socket resources while True: packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if not timedout and opcode == common.FEEDBACK_OP: break print('Final feedback:', packet[1]) common.release_rsrcs([sender, receiver]) if packet[1] == common.ERROR_OP: common.exit_err(f'Error: could not properly send {filename}, ' \ + 'aborting...\n', 1) elif packet[1] == common.DONE_OP: print(f'\nCompleted: sent {filename}', file=sys.stderr) else: print('Unable to confirm final transmission status.')
def send_metadata(sender, endpoint, receiver, filename, datasize): filename_sent = False datasize_sent = False checksum_sent = False # Transmit the filename in a packet # The filename is a byte string of FNAME_LEN length; the single-byte prefix # for filename is FNAME_OP fname_bytes = (common.FNAME_OP + filename[:common.FNAME_LEN]).encode() print('Filename sent:', fname_bytes) while not filename_sent: sender.sendto(fname_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if not timedout and opcode == common.FEEDBACK_OP \ and packet[1] == common.ACK_OP: filename_sent = True print('Filename feedback:', packet[1]) # Transmit the data size in a packet # The data size is a byte string of DATASIZE_LEN length; the single-byte # prefix for data size is DATASIZE_OP datasize_bytes = (common.DATASIZE_OP \ + str(datasize)[:common.DATASIZE_LEN]).encode() print('Data size sent:', datasize_bytes) while not datasize_sent: sender.sendto(datasize_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if not timedout and opcode == common.FEEDBACK_OP \ and (packet[1] == common.ACK_OP or packet[1] == common.STOP_OP): datasize_sent = True if packet[1] == common.STOP_OP: common.release_rsrcs([sender, receiver]) common.exit_err('Aborting due to receiver request...', 0) print('Data size feedback:', packet[1]) # Generate and transmit SHA-2 256-bit checksum for file being transmitted in # a packet, the hexadecimal digest transmitted is a 64-byte string; the # single-byte prefix for checksum is HASH_OP, the string length of the # digest is also found as HASH_LEN hexdigest = common.hash_file(common.BLOCKSIZE, filename) hexdigest_bytes = (common.HASH_OP + hexdigest).encode() print('Checksum sent:', hexdigest_bytes) while not checksum_sent: sender.sendto(hexdigest_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if not timedout and opcode == common.FEEDBACK_OP \ and packet[1] == common.ACK_OP: checksum_sent = True print('Checksum feedback:', packet[1])
def mc_recv_file(fromnicip, mcgrpip, mcport_recv, mcport_send, use_buffer): # 1. Get a socket to receive data with and a socket to send data with receiver = common.prep_receiver(fromnicip, mcgrpip, mcport_recv) endpoint = (mcgrpip, mcport_send) sender = common.prep_sender(fromnicip) # 2. Receive metadata filename, datasize, remote_checksum = recv_metadata(receiver, sender, \ endpoint) # The buffer is just a hash table, where each key is a sequence number and # each value is the file content in bytes; this is only used if the option # is chosen by user content_buffer = {} # 3. Receive file content more = '1' while True: if more == '1': packet, opcode, _ = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, None) if opcode != common.CONTENT_OP: continue else: break seq_num = int(packet[1:common.SEQ_NUM_LEN + 1]) print("Received file packet number:", seq_num) more = packet[common.SEQ_NUM_LEN + 1:common.SEQ_NUM_LEN + 2] content = packet[common.SEQ_NUM_LEN + 2:common.BLOCKSIZE \ + common.SEQ_NUM_LEN + 2] if not use_buffer: write_bytes(filename, common.BLOCKSIZE, seq_num, content.encode()) else: content_buffer[seq_num] = content.encode() # Write the buffer to the file if user picked this option if use_buffer: write_buffer(filename, content_buffer) # 4. Check if local data matches remote and send feedback error = None local_checksum = common.hash_file(common.BLOCKSIZE, filename) if local_checksum != remote_checksum: sender.sendto((common.FEEDBACK_OP + common.ERROR_OP).encode(), endpoint) error = True else: sender.sendto((common.FEEDBACK_OP + common.DONE_OP).encode(), endpoint) error = False print("Checksum of local data:", local_checksum) # 5. Release socket resources and indicate success/error to user common.release_rsrcs([sender, receiver]) if error: common.exit_err(f'Error: could not properly receive {filename}, ' \ + 'aborting...\n', 1) else: print(f'Completed: received {filename}\n', file=sys.stderr)
def recv_metadata(receiver, sender, endpoint): filename_rcvd = False datasize_rcvd = False checksum_rcvd = False # Check free space on current working directory _, _, free = shutil.disk_usage(__file__) # Wait for metadata while not filename_rcvd or not datasize_rcvd or not checksum_rcvd: packet, opcode, _ = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, None) if opcode == common.FEEDBACK_OP and packet[1] == common.STOP_OP: common.release_rsrcs([sender, receiver]) common.exit_err('Aborting due to sender request...', 0) # Receive the filename packet # The filename is a byte string of FNAME_LEN length; the single-byte # prefix for filename is FNAME_OP if opcode == common.FNAME_OP: filename = packet[1:common.FNAME_LEN + 1] filename_rcvd = True print("Filename received:", filename) sender.sendto((common.FEEDBACK_OP + common.ACK_OP).encode(), \ endpoint) # Receive the data size packet # The data size is a byte string of DATASIZE_LEN length; the single-byte # prefix for data size is DATASIZE_OP if opcode == common.DATASIZE_OP: datasize = int(packet[1:common.DATASIZE_LEN + 1]) datasize_rcvd = True print("Data size received:", datasize) if free < datasize: # Tell sender to stop sending anything and exit. common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, f'Not enough ' \ + 'free space available for file, aborting...', 1) sender.sendto((common.FEEDBACK_OP + common.ACK_OP).encode(), \ endpoint) # Receive the remote checksum packet # The checksum is a 64-byte string hexadecimal digest; the single-byte # prefix for checksum is HASH_OP, the string length of the digest is # also found as HASH_LEN if opcode == common.HASH_OP: checksum = packet[1:common.HASH_LEN + 1] checksum_rcvd = True print("Checksum received:", checksum) sender.sendto((common.FEEDBACK_OP + common.ACK_OP).encode(), \ endpoint) return (filename, datasize, checksum)
def send_metadata(sender, endpoint, receiver, filename, datasize): # Transmit the filename in a packet # The filename is a byte string of FNAME_LEN length; the single-byte prefix # for filename is FNAME_OP fname_bytes = (common.FNAME_OP + filename[:common.FNAME_LEN]).encode() print('Filename sent:', fname_bytes) sender.sendto(fname_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if timedout: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Timed out, aborting...', 1) if opcode != common.FEEDBACK_OP: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Feedback invalid, aborting', 1) print('Filename feedback:', packet[1]) # Transmit the data size in a packet # The data size is a byte string of DATASIZE_LEN length; the single-byte # prefix for data size is DATASIZE_OP datasize_bytes = (common.DATASIZE_OP \ + str(datasize)[:common.DATASIZE_LEN]).encode() print('Data size sent:', datasize_bytes) sender.sendto(datasize_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if timedout: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Timed out, aborting...', 1) if opcode != common.FEEDBACK_OP: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Feedback invalid, aborting', 1) print('Data size feedback:', packet[1]) if packet[1] == common.STOP_OP: common.release_rsrcs([sender, receiver]) common.exit_err('Aborting due to receiver request...', 0) # Generate and transmit SHA-2 256-bit checksum for file being # transmitted in a packet, the hexadecimal digest transmitted is a # 64-byte string; the single-byte prefix for checksum is HASH_OP, the # string length of the digest is also found as HASH_LEN hexdigest = common.hash_file(common.BLOCKSIZE, filename) hexdigest_bytes = (common.HASH_OP + hexdigest).encode() print('Checksum sent:', hexdigest_bytes) sender.sendto(hexdigest_bytes, endpoint) packet, opcode, timedout = \ common.get_packet(receiver, common.MAX_PACKET_SIZE, \ common.BLOCKING_WAIT) if timedout: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Timed out, aborting...', 1) if opcode != common.FEEDBACK_OP: common.make_clean_exit(sender, receiver, (common.FEEDBACK_OP \ + common.STOP_OP).encode(), endpoint, \ 'Feedback invalid, aborting', 1) print('Checksum feedback:', packet[1])