mercredi 7 octobre 2020

Best design pattern for a network packets parser

I'm developping an OOP application in C# which takes as input a binary stream from a packet sniffer such as Wireshark. This application would parse the binary stream, identify the type of datagram and create and populate the appropriate Object.

A Ethernet Datagram can encapsulate a Network Datagram (IPv4, IPv6...) which can encapsulate a Transport Datagram (TCP, UDP...)

What would be the most suitable design pattern ?

I thought Abstract Factory could be good.

Input (assuming the application is always taking a Ethernet datagram as input)

0000   01 02 03 04 05 06 07 08 09 0A 0B 0C 08 00 45 00   ..... ..3.y/..E.
0010   00 28 df 34 40 00 80 06 ad 8a cc 05 4a ee d1 23   .(.4@.......*...
0020   05 1c f3 0b 0c 38 30 4c 7f f1 50 0d d2 7c 50 10   .....80L..P..|P.
0030   02 02 6d b6 00 00  

Pseudo code :

abstract class EthernetFactory
{
    public static EthernetDatagram MakeInstance(ref List<byte> stream)
    {
        EthernetDatagram ethernet = new EthernetDatagram();
        ...
        ethernet.Destination = 01 02 03 04 05 06;
        ethernet.Source = 07 08 09 0A 0B 0C;
        
        switch (type)
        {
            case IPV4: // 0x8
                ethernet.NetworkLayerDatagram = (NetworkLayerDatagram) IPv4DatagramFactory.MakeInstance(ref List<byte> stream);
                break;

            case IPV6:
                ethernet.NetworkLayerDatagram = (NetworkLayerDatagram) IPv6DatagramFactory.MakeInstance(ref List<byte> stream);
                break;
        }
        
        ...
        
        return ethernet;
    }
}

abstract class IPv4DatagramFactory : NetworkLayerDatagramFactory
{
    public static IPv4Datagram MakeInstance(ref List<byte> stream)
    {
        IPv4Datagram ipv4 = new IPv4Datagram();
        ipv4.Source = cc 05 4a ee;
        ipv4.Destination = d1 23 05 1c;
        
        ...
        
        switch (type)
        {
            case TCP:
                ipv4.TransportLayerDatagram = (TransportLayerDatagram) TCPDatagramFactory.MakeInstance(ref List<byte> stream);
                break;
            
            case UDP:
                ipv4.TransportLayerDatagram = (TransportLayerDatagram) UDPDatagramFactory.MakeInstance(ref List<byte> stream);
                break;
        }
        
        ...
        
        return ipv4;
    }
}

abstract class IPv6DatagramFactory : NetworkLayerDatagramFactory
{
    ...
}

abstract class NetworkLayerDatagram
{
    public ... Source = {get; private set}
    public ... Destination = {get; private set}
    public TransportLayerDatagram TransportLayerDatagram {get; private set}
}

class IPv4Datagram : NetworkLayerDatagram
{
}

class IPv6Datagram : NetworkLayerDatagram
{
}

abstract class TransportLayerDatagram
{
    public uint SourcePort = {get; private set}
    public uint DestPort = {get; private set}
    public ApplicationLayerDatagram ApplicationLayerDatagram {get; private set}
}

class TCPDatagram : TransportLayerDatagram
{
}

class UDPDatagram : TransportLayerDatagram
{
}

As you can see, the top-level factory consume the stream to take a decision then pass data to a child factory etc... Is it a good practice ?

Thanks for your help...

Aucun commentaire:

Enregistrer un commentaire