Open In App

Python – Stop & Wait Implementation using CRC

Stop and wait protocol is an error control protocol, in this protocol the sender sends data packets one at a time and waits for positive acknowledgment from the receiver’s side, if acknowledgment is received then the sender sends the next data packet else it’ll resend the previous packet until a positive acknowledgment is not received. 

Note: To get more info on what is stop and wait protocol, refer Stop and Wait ARQ article.



CRC aka Cyclic redundancy check is an error detection mechanism, its procedure is as follows.

Sender side

Receiver side

In this article, we will implement stop and wait algorithm such that the sender computes error detecting code using CRC and sends the data along with error detecting code to the receiver using socket. The receiver, after ensuring an error-free frame, saves the data.



The procedure will be as follows:

At sender

At receiver:

At sender

Final outcome: Input and output files should match. A log file should show how many frames were in error and how many retries were done for each frame. 

Error Generation 

Error is introduced as follows :

Save this file as error_gen.py.




import random
 
class err_gen() :
 
    def induce_err(self, in_str) :
        chk = (int)(random.random() * 1000) % 2
         
        if not chk :
            return in_str
 
        idx = (int)(random.random() * 1000) % len(in_str)
        f_bit = '*'
        if in_str[idx] == '0':
            f_bit = '1'
        else :
            f_bit = '0'
 
        out_str = in_str[ : idx] + f_bit + in_str[idx + 1 : ]
        return out_str
 
if __name__ == "__main__":
    data = "1001010"
    print("Initial : ", data)
    print("Final : ", err_gen().induce_err(data))

Output:

Initial :  1001010
Final :  0001010

Modulo two Division

Modulo two-division is required to calculate the CRC. Explaining the working of this code is beyond the scope of this article, the curious ones can refer Modulo two division.

Save this file as calc.py.




def xor(a, b):
    result = []
    for i in range(1, len(b)):
        if a[i] == b[i]:
            result.append('0')
        else:
            result.append('1')
 
    return ''.join(result)
 
 
def mod2div(dividend, divisor):
    pick = len(divisor)
    tmp = dividend[0: pick]
 
    while pick < len(dividend):
 
        if tmp[0] == '1':
            tmp = xor(divisor, tmp) + dividend[pick]
        else:
            tmp = xor('0'*pick, tmp) + dividend[pick]
        pick += 1
 
    if tmp[0] == '1':
        tmp = xor(divisor, tmp)
    else:
        tmp = xor('0'*pick, tmp)
 
    return tmp
 
 
if __name__ == "__main__":
    dividend = "10010101"
    divisor = "011010"
    print(dividend + " % " + divisor + " = " + mod2div(dividend, divisor))

Output:

10010101 % 011010 = 01001

Configuration Data

These are some constants that are shared by the receiver and the sender save this file as configuration.py.




# Number of characters to be read
# at once.
FRAME_SIZE = 10
 
# Buffer size of tcp socket
BUFFER_SIZE = 1024 * 10
 
# Constant to determine the end
# of file and transaction.
END_OF_FILE = "##**##**##**##**##"
 
# CRC generator key
CRC_GENERATOR = "10110100110101110011010101110100000101"
 
# Accept and reject acknowledgements
# from receiver to the sender.
REJECT = "NAK"
ACCEPT = "OK"

Client

The client class will have five methods.

Save this file as CLIENT.py.




from calc import mod2div
from error_gen import err_gen
from configuration import *
import socket
 
 
class Client:
 
    def __init__(self, ipadd, portn):
        self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket_.connect((ipadd, portn))
 
    def asciiToBin(self, data):
       
        # ascii to bin.
        return bin(int.from_bytes(data.encode(), 'big'))
 
    def appendZero(self, message):
       
        # append n - 1 0's.
        message = (message.ljust(len(CRC_GENERATOR) - 1 + len(message), '0'))
        return message
 
    def encode(self, data):
 
        # convert ascii to bin
        message = self.asciiToBin(data)
        dividend = self.appendZero(message)
 
        # generate and append crc
        crc = mod2div(dividend, CRC_GENERATOR)
        curr_frame = (message + crc)
 
        return curr_frame
 
    def send_file(self, filename='file.txt'):
 
        f = open(filename)
        data = f.read(FRAME_SIZE)
 
        while len(data) > 0:
 
            # encode data
            curr_frame = self.encode(data)
 
            # induce error
            curr_frame = err_gen().induce_err(curr_frame)
 
            # send frame
            self.socket_.send(curr_frame.encode())
 
            # receive acknowledgement
            if self.socket_.recv(BUFFER_SIZE).decode() == 'OK':
                data = f.read(FRAME_SIZE)
 
        # Terminate session
        self.socket_.send(END_OF_FILE.encode())
        self.socket_.close()
        f.close()
        print("File sent")
 
 
newclient = Client(ipadd="127.0.0.1", portn=3241)
newclient.send_file(filename="file.txt")

Server

The server class will have six methods.

Save this file as SERVER.py




import socket
from calc import mod2div
from configuration import *
 
 
class Server:
 
    def __init__(self, ipaddr, portn):
 
        self.socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket_.bind((ipaddr, portn))
        self.socket_.listen(5)
 
    def iszero(self, data):
        for x in data:
            if x != '0':
                return False
        return True
 
    def isCurrupted(self, message):
        return not self.iszero(mod2div(message, CRC_GENERATOR))
 
    def decode(self, message):
        message = message[: 1 - len(CRC_GENERATOR)]
        n = int(message, 2)
        return n.to_bytes((n.bit_length() + 7) // 8, 'big').decode()
 
    def log(self, loghandle, itr, received_frame, retrie_count):
 
        loghandle.write("Frame Number : " + str(itr) + "\n")
        loghandle.write("Frame Content : \"" +
                        self.decode(received_frame) + "\"\n")
        loghandle.write("Retries : " + str(retrie_count) + "\n\n")
 
    def receive_file(self, filepath, logpath):
 
        received_socket, addr = self.socket_.accept()
 
        f = open(filepath, 'w')
        l = open(logpath, 'w')
 
        itr = 1
        retrie_count = 0
 
        while 1:
 
            received_frame = received_socket.recv(BUFFER_SIZE).decode()
 
            if received_frame == END_OF_FILE:
                f.close()
                l.close()
                self.socket_.close()
                print("File Received")
                return
 
            if self.isCurrupted(received_frame):
                retrie_count += 1
                received_socket.send(REJECT.encode())
 
            else:
                # Received file
                f.write(self.decode(received_frame))
 
                # Log
                self.log(l, itr, received_frame, retrie_count)
 
                retrie_count = 0
                received_socket.send(ACCEPT.encode())
 
newServer = Server(ipaddr="127.0.0.1", portn=3241)
newServer.receive_file(filepath="received_data.txt", logpath="logfile.txt")

To see the execution of the code, keep all the files in a single directory, and execute the server file before executing the client’s file. Also, make sure that you have the input text file and its correct path.

Execution:

Program execution process


Article Tags :