Share

Protocol Stack Design Pattern

Intent

We have already seen that Protocol Layer and Protocol Packet provide a standardized interface between different layers of a protocol. The Protocol Stack design pattern takes advantage of the layer decoupling and provides a mechanism for dynamic insertion and removal of protocol layers from a stack.

Also Known As

  • Protocol Layer Manager
  • Protocol Layer Collection

Motivation

Protocol stacks tend to be rigid in design and protocol layers cannot be dynamically added or removed from a protocol stack. This limits the use of protocol stacks in the even changing world of protocol standards. There are several scenarios where the layers in a protocol stack need to be changed on the fly. A few examples are:

  • An application detects the failure of the physical layer and decides to use a different medium to transport data. The application decides to change the lower layers of the protocol while keeping the upper layers intact.
  • The user has enabled encryption and this requires the sandwiching of the encryption layer between the network layer and the data-link layer.
  • The protocol stack designer needs to debug the interactions between the network layer and transport layer. This can be accomplished by sandwiching a pass-through logging layer that stores all the messages that get exchanged between the layers.

The Protocol Stack design pattern addresses this issue and introduces a flexible architecture for dynamic addition and removal of protocol layers.

Applicability

The Protocol Stack Design pattern can be used to implement any type of layered protocol. It can be also used when different operations on an entity need to be performed in a pipeline fashion. Each stage of the pipeline could be modeled as a layer. This pattern is particularly useful in applications involving dynamic layer manipulation. A few applications are:

  • Changing the physical layer when the application detects failure of the physical layer.
  • Dynamically adding layers to handle demands from the user session (e.g. enabling encryption)
  • Debugging inter-layer interactions by adding a pass-through debug only layer.
  • Testing higher layers of a protocol by adding a special loop-back layer to connect the lower layer transmit and receive.
  • Emulating a node by configuring a echo-back layer to connect the higher layer transmit and receive.

Structure

The Protocol Stack Design Pattern is implemented by the Protocol Stack class. This class maintains a doubly linked list of active layers.

Participants

The key actors of this design pattern:

  • Protocol Stack: This class maintains a doubly linked list of Protocol layers. It supports dynamic addition and removal of protocol layers.
  • Protocol Layer: This is the base class for all protocol layers. The individual layers interface with each other via pointers to this class. The actual type of the upper layer and lower layer classes is not known to the implementers of a certain layer.

Collaboration

The following diagram shows the relationship and collaboration between various classes needed for the Datalink layer example. 

Collaboration graph for protocol stack

Consequences

The Protocol Stack design pattern breaks down the rigid protocol layer structure and provides a very flexible solution where layers can be dynamically added and removed from the stack.

The figure below shows the flexibility of the pattern in supporting different layer organizations. The examples in the figure demonstrate:

  • A debug pass-through layer that displays the messages being exchanged between the datalink layer and the physical layer.
  • A loopback layer that facilitates the testing of the datalink and network layers by just looping back all transmitted messages back for receive.
  • An echo-back layer allows the protocol stack to emulate a node by just echoing back all higher layer messages back for transmission.
  • An encryption layer sandwiched between the datalink and physical layers. This layers encrypts and decrypts data that is passed between these layers.

Protocol stack configurations with debug layer, a loopback layer, and echo back layer and an encryption layer.

Implementation

The Protocol Stack is implemented as a single class. The class maintains a doubly linked list of Protocol Layers. Important methods of the class are:

  • Handle_Transmit: This handler is invoked by the application to transmit messages using the protocol stack.
  • Handle_Receive: This handler is invoked by the device to pass received messages to the protocol stack.
  • Add_Layer: Add a protocol layer at a specific position in the protocol stack.
  • Remove_Layer: Remove a layer from the protocol stack.

Sample Code and Usage

The code for the Protocol Stack class is presented below:

Protocol Stack Header File

#ifndef PROTOCOL_STACK_H
#define PROTOCOL_STACK_H

#include <stdio.h>

class Protocol_Packet;
class Protocol_Layer;


class Protocol_Stack
{
public:

    enum Placement
    {
        TOP,  
        ABOVE,
        BELOW
    };

    void Handle_Transmit(Protocol_Packet *p_Packet);
    void Handle_Receive(Protocol_Packet *p_Packet);

    void Add_Layer(Protocol_Layer *p_Layer, Placement placement = TOP, 
                   Protocol_Layer *p_Existing_Layer = NULL);
    void Remove_Layer(Protocol_Layer *p_Layer);

    Protocol_Stack();

private:

    Protocol_Layer *m_p_Highest_Layer;

    Protocol_Layer *m_p_Lowest_Layer;
};
#endif

Protocol Stack Source File

#include "Protocol_Stack.h"
#include "Protocol_Layer.h"
#include <assert.h>


void Protocol_Stack::Handle_Transmit(Protocol_Packet *p_Packet)
{
    if (m_p_Highest_Layer)
    {
        m_p_Highest_Layer->Transmit(p_Packet);
    }
}


void Protocol_Stack::Handle_Receive(Protocol_Packet *p_Packet)
{
    if (m_p_Lowest_Layer)
    {
        m_p_Lowest_Layer->Handle_Receive(p_Packet);
    }
}


void Protocol_Stack::Add_Layer(Protocol_Layer *p_Layer, Placement placement, 
                                 Protocol_Layer *p_Existing_Layer)
{
    // Start with a clean slate. Initialize the upper and lower protocol
    // layers to NULL. The pointers will be suitably initialized after insertion.

    p_Layer->Set_Lower_Layer(NULL);
    p_Layer->Set_Upper_Layer(NULL);

    // Check if some other layer is already present in the protocol stack. 
    // The placement processing applies only if this is not the first layer 
    // being added to the stack.
    if (m_p_Highest_Layer)
    {
        // This is not the first layer

        switch (placement)
        {
        case TOP:   // Add the layer at the top
            assert(p_Existing_Layer == NULL);
            m_p_Highest_Layer->Set_Upper_Layer(p_Layer);
            p_Layer->Set_Lower_Layer(m_p_Highest_Layer);
            m_p_Highest_Layer = p_Layer;
            break;

        case ABOVE: // Place the layer above the existing layer
            assert(p_Existing_Layer);

            Protocol_Layer *p_Previous_Upper_Layer;

            // Linking up the new layer above the existing layer
            p_Previous_Upper_Layer = p_Existing_Layer->Get_Upper_Layer();
            p_Layer->Set_Upper_Layer(p_Previous_Upper_Layer);
            p_Layer->Set_Lower_Layer(p_Existing_Layer);
            p_Existing_Layer->Set_Upper_Layer(p_Layer);

            // Check if the existing layer was the highest layer
            if (p_Existing_Layer == m_p_Highest_Layer)
            {
                // If it was, make the new layer the highest layer
                m_p_Highest_Layer = p_Layer;
            }
            else
            {
                // Change the pointer of the existing layer's upper layer
                // to point to the newly inserted layer.
                p_Previous_Upper_Layer->Set_Lower_Layer(p_Layer);
            }
            break;

        case BELOW:    // Place the layer below the existing layer

            assert(p_Existing_Layer);
            Protocol_Layer *p_Previous_Lower_Layer;

            // Linking up the new layer below the existing layer
            p_Previous_Lower_Layer = p_Existing_Layer->Get_Lower_Layer();
            p_Layer->Set_Upper_Layer(p_Existing_Layer);
            p_Layer->Set_Lower_Layer(p_Previous_Lower_Layer);
            p_Existing_Layer->Set_Lower_Layer(p_Layer);

            // Check if the existing layer was the lowest layer
            if (p_Existing_Layer == m_p_Lowest_Layer)
            {
                // If it was, make the new layer the lowest layer
                m_p_Lowest_Layer = p_Layer;
            }
            else
            {
                // Change the pointer of the existing layer's lower layer
                // to point to the newly inserted layer.
                p_Previous_Lower_Layer->Set_Upper_Layer(p_Layer);
            }
            break;

        }
    }
    else  // The highest layer is NULL
    {
        // This means that this is the first layer in the protocol stack.
        assert(p_Existing_Layer == NULL);
        m_p_Highest_Layer = p_Layer;
        m_p_Lowest_Layer = p_Layer;
    }
}


void Protocol_Stack::Remove_Layer(Protocol_Layer *p_Layer)
{
    // Check if the layer to be removed is the highest layer.
    if (p_Layer == m_p_Highest_Layer)
    {
        // Yes it is, so set the removed layer's lower layer as the highest layer
        // in the protocol stack.
        m_p_Highest_Layer = p_Layer->Get_Lower_Layer();

        // If this was not the only layer in the stack, set the
        // upper layer of this layer as NULL.
        if (m_p_Highest_Layer)
        {
            m_p_Highest_Layer->Set_Upper_Layer(NULL);
        }
    }
    else // Not the highest layer
    {
        // Stitch the upper layer to lower layer link after the layer is removed.
        (p_Layer->Get_Upper_Layer())->Set_Lower_Layer(p_Layer->Get_Lower_Layer());
    }

    // Check if the layer to be removed is the lowest layer.
    if (p_Layer == m_p_Lowest_Layer)
    {
        // Yes it is, so set the removed layer's upper layer as the lowest layer
        // in the protocol stack.
        m_p_Lowest_Layer = p_Layer->Get_Upper_Layer();

        // If this was not the only layer in the stack, set the
        // lower layer of this layer as NULL.
        if (m_p_Lowest_Layer)
        {
            m_p_Lowest_Layer->Set_Lower_Layer(NULL);
        }
    }
    else
    {
        // Stitch the lower layer to upper layer link after the layer is removed.
        (p_Layer->Get_Lower_Layer())->Set_Upper_Layer(p_Layer->Get_Upper_Layer());
    }

    // Set the upper and lower layer pointers of the removed layer as NULL.
    // This is a safety measure.
    p_Layer->Set_Lower_Layer(NULL);
    p_Layer->Set_Upper_Layer(NULL);
}


Protocol_Stack::Protocol_Stack()
{
    m_p_Highest_Layer = NULL;
    m_p_Lowest_Layer = NULL;
}

Known Uses

The Protocol Stack design pattern can be used where ever a layered but decoupled organization is required.

Related Patterns/Principles