classdef pcapReader < handle & comm.internal.ConfigBase
    %pcapReader Create PCAP file reader object
    %   PCAP = pcapReader(FILENAME) creates a PCAP file reader object to
    %   read protocol packets from the input PCAP file, FILENAME.
    %
    %   PCAP = pcapReader(FILENAME, Name=Value) creates a PCAP file reader
    %   object to read protocol packets from the input PCAP file, FILENAME,
    %   with the specified property Name set to the specified Value. You
    %   can specify additional name-value arguments in any order as
    %   (Name1=Value1, ..., NameN=ValueN).
    %
    %   FILENAME is the name of the PCAP file including the file extension,
    %   specified as a character row vector or a string scalar.
    %
    %   The pcapReader object reads and decodes Ethernet and enhanced
    %   common public radio interface (eCPRI) protocol packets based on
    %   specific criteria. You can also plug in custom protocol decoders.
    %
    %   pcapReader properties (configurable):
    %
    %   OutputTimestampFormat - Output format for the packet timestamp,
    %                           specified as 'microseconds', 'seconds', or
    %                           'datetime'. The default value is
    %                           'microseconds'.
    %
    %
    %   pcapReader properties (read-only):
    %
    %   MajorVersion          - Major version of the PCAP file format,
    %                           returned as a nonnegative scalar
    %   MinorVersion          - Minor version of the PCAP file format,
    %                           returned as a nonnegative scalar
    %   SnapLength            - Maximum length of the packet in the PCAP
    %                           file, returned as a nonnegative scalar
    %   LinkType              - Link layer header type in the PCAP global
    %                           header, returned as a nonnegative scalar
    %   LinkName              - Name of the link layer header type given by
    %                           the PCAP file reader object, returned as a
    %                           character vector
    %   NanoSecResolution     - Flag to indicate whether the PCAP file has
    %                           nanosecond resolution for the packet
    %                           timestamp, returned as 1 (true) or 0
    %                           (false)
    %
    %   pcapReader methods:
    %
    %   read                  - Read next protocol packet from PCAP file
    %   readAll               - Read all protocol packets from current
    %                           position to the end of PCAP file
    %   reset                 - Reset position of PCAP file reader to first
    %                           protocol packet of PCAP file
    %   addLinkTypeDecoder    - Add a custom link layer protocol decoder to
    %                           PCAP file reader
    %   addUpperLayerDecoder  - Add a custom upper-layer protocol decoder
    %                           to PCAP file reader
    %
    %   % Example 1:
    %   %  Read all the packets from the ethernetSamplePackets.pcap file to
    %   %  the MATLAB workspace.
    %
    %   % Create a PCAP file reader object with filename as
    %   % 'ethernetSamplePackets.pcap'
    %   pcapReaderObj = pcapReader('ethernetSamplePackets.pcap')
    %
    %   % Read all the Ethernet packets to the MATLAB workspace
    %   ethPackets = readAll(pcapReaderObj)
    %
    %   % Example 2:
    %   %  Read the packets in the streaming mode (packet by packet) from
    %   %  the ethernetSamplePackets.pcap file to the MATLAB workspace.
    %   %  Read the packets with Ethernet source address as '44FB5A9710AC'
    %   %  and Ethernet type as IPv4 (2048).
    %
    %   % Create a PCAP file reader object with filename as
    %   % 'ethernetSamplePackets.pcap'
    %   pcapReaderObj = pcapReader('ethernetSamplePackets.pcap', ...
    %                              OutputTimestampFormat='datetime');
    %
    %   % Create a filter for the Ethernet source address and Ethernet type
    %   filterString = ['eth.SourceAddress == 44FB5A9710AC && ' ...
    %               'eth.Type == 2048'];
    %
    %   % Read the packets (in the streaming mode) matching with the
    %   % configured filter to the MATLAB workspace
    %   for idx = 1:10
    %       ethPacket = read(pcapReaderObj, filterString)
    %   end
    %
    %   % Example 3:
    %   %  Read the eCPRI packets from the ethernetSamplePackets.pcap file
    %   %  to the MATLAB workspace with eCPRI message type as 'IQdata',
    %   % 'BitSequence', or 'RemoteReset'.
    %
    %   % Create a PCAP file reader object with filename as
    %   % 'ethernetSamplePackets.pcap'
    %   pcapReaderObj = pcapReader('ethernetSamplePackets.pcap')
    %
    %   % Create a filter for the eCPRI message types
    %   filterString = ['ecpri.MessageType == IQData || ecpri.MessageType == BitSequence ' ...
    %               '|| ecpri.MessageType == RemoteReset'];
    %
    %   % Read the packets with the specified filters to the MATLAB workspace
    %   ecpriFilteredFirstPacket = read(pcapReaderObj, filterString)
    %   ecpriFilteredSecondPacket = read(pcapReaderObj, filterString)
    %   ecpriFilteredRemainingPackets = readAll(pcapReaderObj, filterString)
    %
    %   % Reset the PCAP file reader object
    %   reset(pcapReaderObj);
    %
    %   % Create a new filter on the same PCAP file
    %   filterString = 'ecpri.MessageType == IQData';
    %
    %   % Read the packets with the specified filters to the MATLAB workspace
    %   ecpriFilteredPackets = readAll(pcapReaderObj, filterString)
    %
    %   See also pcapWriter, pcapngWriter

    %   Copyright 2021-2023 The MathWorks, Inc.

    properties (GetAccess = public, SetAccess = private)
        %Filename Name of the PCAP file including the file extension
        %   Specify filename as a character row vector or a string scalar.
        Filename = ''

        %OutputTimestampFormat Output format for the packet timestamp
        %   Specify timestamp format as 'datetime', 'seconds', or
        %   'microseconds'. The default value is 'microseconds'.
        OutputTimestampFormat = 'microseconds'
    end

    properties (GetAccess = public, SetAccess = private)
        %MajorVersion Major version of the PCAP file format, returned as a
        %nonnegative scalar (2 bytes)
        MajorVersion = 0

        %MinorVersion Minor version of the PCAP file format, returned as a
        %nonnegative scalar (2 bytes)
        MinorVersion = 0

        %SnapLength Maximum length of the packet in the PCAP file, returned
        %as a nonnegative scalar (4 bytes)
        SnapLength = 0

        %LinkType Link layer header type in the PCAP global header,
        %returned as a nonnegative scalar (4 bytes)
        LinkType = 0

        %LinkName Name of the link layer header type given by the PCAP file
        %reader object, returned as a character vector
        LinkName = blanks(0)

        %NanoSecResolution Flag to indicate whether the PCAP file has
        %nanosecond resolution for the packet timestamp, returned as 1
        %(true) or 0 (false).
        NanoSecResolution = false
    end

    properties (GetAccess = public, SetAccess = private, Hidden)
        %MagicNumber Magic number indicates the byte ordering of the PCAP
        %file, returned as a character vector representing a 4-octet
        %hexadecimal number (4 bytes)
        MagicNumber = blanks(0)
    end

    properties (Constant, Hidden)
        % Allowed output timestamp format values
        OutputTimestampFormatValues = {'datetime', 'seconds', 'microseconds'}
        % Allowed data types when configuring the protocol filter
        SupportedFilterTypes = {'string', 'hexadecimal', 'numeric'};
    end

    properties (GetAccess = public, SetAccess = private)
        %PacketsRead Total number of packets read
        PacketsRead = 0
    end

    properties (Access = private)
        %pFID File identifier of the opened PCAP file
        pFID = -1

        %pTimestampFormat Specifies the timestamp format
        % 0 - datetime
        % 1 - seconds
        % 2 - microseconds
        pTimestampFormat = 2

        %pValueFrom2Bytes Anonymous function to convert the 2-bytes into
        %the equivalent decimal value
        pValueFrom2BytesFcn

        %pValueFrom4Bytes Anonymous function to convert the 4-bytes into
        %the equivalent decimal value
        pValueFrom4BytesFcn

        %pEmptyStructure Empty structure
        pEmptyStructure = struct()

        %pEmptyPacket Empty packet structure
        pEmptyPacket = struct('SNo', 0, ...  % Serial number given to the packets
            'Timestamp', 0, ...             % Timestamp of the packet in specified format
            'LinkType', 0, ...              % Link type number of the protocol
            'Protocol', blanks(0), ...      % Protocol name
            'PacketLength', 0, ...          % Packet length in bytes
            'Packet', 0, ...                % Decoded packet
            'RawBytes', zeros(1,0));        % Remaining bytes after decoding the packet
    end

    properties (SetAccess = private, GetAccess = public)
        %LinkTypeDecoders Decoder function handles based on the PCAP link
        %type
        % The LinkTypeDecoders represented as an array of structures with the
        % fields: ProtocolName, LinkType and Decoder.
        LinkTypeDecoders = struct('ProtocolName', '', ...
            'LinkType', 0, ...
            'Decoder', [])
        LinkTypeDecoderCount = 0

        %Decoders Decoder function handles of all the supported protocols
        % The Decoders represented as an array of structures with the
        % fields: protocol name, ProtocolName, and the respective decoder
        % function handle, Decoder.
        Decoders = struct('ProtocolName', '', ...
            'Decoder', [])
        DecoderCount = 0

        %UpperLayerDecoders Decoder function handles of all the supported
        %protocols
        % The UpperLayerDecoders represented as an array of structures with
        % the fields: ProtocolName, FieldName, FieldValue,
        % UpperProtocolName and Decoder function handle.
        UpperLayerDecoders = struct('ProtocolName', '', ...
            'FieldName', blanks(0), ...
            'FieldValue', 0, ...
            'UpperProtocolName', blanks(0), ...
            'UpperDecoder', [])
        UpperLayerDecoderCount = 0

        %FilterConfig Configuration of the filter of each field in the
        %protocol decoders output structure
        FilterConfig = struct('ProtocolName', '', ...
            'FieldName', '', ...
            'Type', '');
        FilterConfigCount = 0
    end

    methods
        function pcapReaderObj = pcapReader(filename, varargin)
            % Validate the number of input arguments
            narginchk(1, 3);

            % Name-value pair check
            coder.internal.errorIf(mod(nargin-1,2) == 1, 'MATLAB:system:invalidPVPairs');

            % Validate the filename
            validateattributes(filename, {'char' ,'string'}, {'scalartext'}, ...
                mfilename, 'Filename');
            pcapReaderObj.Filename = char(filename);

            % Apply name-value pairs
            for i = 1:2:nargin-1
                value = validatestring(varargin{i}, {'OutputTimestampFormat'}, mfilename);
                switch value
                    case 'OutputTimestampFormat'
                        pcapReaderObj.OutputTimestampFormat = validatestring(varargin{i+1}, ...
                            pcapReaderObj.OutputTimestampFormatValues, mfilename, 'OutputTimestampFormat');
                        % Convert the timestamp string format into integer
                        % format
                        switch pcapReaderObj.OutputTimestampFormat
                            case 'datetime'
                                pcapReaderObj.pTimestampFormat = 0;
                                % A new field in the output structure is
                                % added to return the timestamp in seconds
                                pcapReaderObj.pEmptyPacket.TimestampSec = 0; % Timestamp of the packet in seconds
                            case 'seconds'
                                pcapReaderObj.pTimestampFormat = 1;
                            case 'microseconds'
                                pcapReaderObj.pTimestampFormat = 2;
                        end
                end
            end

            % Add built-in protocol decoders
            addBuiltInDecoders(pcapReaderObj);

            % Open the PCAP file in read mode
            [pcapReaderObj.pFID, errMsg] = fopen(pcapReaderObj.Filename, 'r');
            coder.internal.errorIf((pcapReaderObj.pFID < 0), ...
                'shared_comm_sysmod:pcapReader:FileOpenError', errMsg);

            % Check whether the file format is PCAPNG
            blockType = [10;13;13;10]; % PCAPNG block type
            bytes = fread(pcapReaderObj.pFID, numel(blockType));
            coder.internal.errorIf(numel(bytes) == numel(blockType) ...
                && all(bytes == blockType), 'shared_comm_sysmod:pcapReader:NoPCAPNGSupport');
            fseek(pcapReaderObj.pFID, 0, 'bof');

            % Read the PCAP global header and identify the PCAP link type
            readGlobalHeader(pcapReaderObj);
        end

        function addUpperLayerDecoder(pcapReaderObj, protocolName, fieldName, ...
                fieldValue, upperLayerProtocolName, upperLayerDecoder, protocolFields)
            %addUpperLayerDecoder Add a custom upper-layer protocol decoder
            %to PCAP file reader
            %
            %   addUpperLayerDecoder(PCAPREADEROBJ, PROTOCOLNAME,
            %   FIELDNAME, FIELDVALUE, UPPERLAYERPROTOCOLNAME,
            %   UPPERLAYERDECODER) adds an upper-layer protocol decoder
            %   function handle, UPPERLAYERPROTOCOLNAME, to the PCAP file
            %   reader, pcap, for decoding the payload in the output of an
            %   existing protocol decoder, PROTOCOLNAME. The FIELDNAME
            %   input specifies the field in the output structure of the
            %   existing protocol decoder, and the FIELDVALUE input
            %   specifies the value of this field name. When the existing
            %   protocol decoder output structure has the field value,
            %   FIELDVALUE, for the specified field name, FIELDNAME, the
            %   PCAP reader object, PCAPREADEROBJ, calls upper-layer
            %   decoder, UPPERLAYERDECODER. The UPPERLAYERPROTOCOLNAME
            %   input specifies a name for the upper-layer protocol
            %   decoder.
            %
            %   addUpperLayerDecoder(PCAPREADEROBJ, PROTOCOLNAME,
            %   FIELDNAME, FIELDVALUE, UPPERLAYERPROTOCOLNAME,
            %   UPPERLAYERDECODER, PROTOCOLFIELDS) additionally specifies
            %   protocol fields, PROTOCOLFIELDS, to filter packets based on
            %   the specified fields of the protocol decoder output
            %   structure.
            %
            %   PCAPREADEROBJ is an object of type <a href="matlab:help('pcapReader')">pcapReader</a>.
            %
            %   PROTOCOLNAME Name of the existing protocol decoder,
            %   specified as a character vector or a string scalar. The
            %   existing protocol decoder can be an Ethernet decoder
            %   ('eth'), an eCPRI decoder ('ecpri'), or any other newly
            %   added protocol decoder.
            %
            %   FIELDNAME Field name in the output structure of the
            %   existing protocol decoder, specified as a character vector
            %   or a string scalar.
            %
            %   FIELDVALUE Value of the field name, specified as a
            %   nonnegative integer, character vector, or string scalar. If
            %   the fieldName input has this value, then this object
            %   functions <a href="matlab:help('pcapReader.read')">read</a> or <a href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a> pass the payload to the upper-layer protocol
            %   decoder function handle.
            %
            %   UPPERLAYERPROTOCOLNAME Name given to the upper-layer
            %   protocol decoder, specified as a character vector or a
            %   string scalar. To store the decoded packet of the
            %   upper-layer protocol decoder, the object functions 
            %   <a href="matlab:help('pcapReader.read')">read</a> or <a href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a>
            %   uses this value to create a new field in the Packet field
            %   of the output decoded protocol packet structure.
            %
            %   UPPERLAYERDECODER Upper-layer protocol decoder that decodes
            %   the payload of the existing protocol decoder and returns
            %   the decoded packet with the processed length, specified as
            %   a function handle. This code shows the syntax of this
            %   argument:
            %
            %       [outputPacket, processedLength] = upperLayerDecoderFunction(existingProtocolPayload);
            %       upperLayerDecoder = @upperLayerDecoderFunction
            %
            %   The upperLayerDecoderFunction is the function that decodes
            %   the payload of the existing protocol decoder,
            %   existingProtocolPayload. The outputPacket contains the
            %   decoded packet as a structure. The processedLength output
            %   is the number of decoded bytes. A negative value of the
            %   processed length indicates failed packet decoding. If
            %   packet decoding fails, the output decoded packet structure,
            %   outputPacket, is empty.
            %
            %   PROTOCOLFIELDS Protocol fields and data types, specified as
            %   a two-column cell array that indicates the protocol fields
            %   and their respective data types. Specify these fields and
            %   data types as character vectors or string scalars. The
            %   first column of the cell array specifies the field name.
            %   The second column of the cell array specifies the data type
            %   of the corresponding field name. The valid values for data
            %   type are 'numeric', 'string' and 'hexadecimal'. This value
            %   specifies the fields (of the protocol decoder output
            %   structure) on which the <a
            %   href="matlab:help('pcapReader.read')">read</a> or <a
            %   href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a>
            %   object functions can specify the packetFilter input.

            % Validate the number of input arguments
            narginchk(6, 7);

            % Validate previous protocol name
            protocolName = validatestring(protocolName, ...
                {pcapReaderObj.Decoders.ProtocolName}, mfilename, 'ProtocolName');

            % Protocol fields
            if nargin == 6
                protocolFields = {};
            end

            % Check for any existing decoders with given protocol name. If
            % the decoder is already added, then throws an error.
            [isExist, ~] = decoderWithName(pcapReaderObj, upperLayerProtocolName);
            coder.internal.errorIf(isExist, ...
                'shared_comm_sysmod:pcapReader:UnableToAddDecoder', ...
                upperLayerProtocolName);

            % Verify that the decoder function is a function handle
            validateattributes(upperLayerDecoder, {'function_handle'}, {'scalar'}, ...
                mfilename, 'upperLayerDecoder');

            % Validate field name
            validateattributes(fieldName, {'char', 'string'}, {'scalartext'}, ...
                mfilename, 'fieldName');

            if nargin >= 6
                % Validate field value
                validateattributes(fieldValue, {'numeric', 'char', 'string'}, ...
                    {}, mfilename, 'fieldValue');
                if isnumeric(fieldValue)
                    validateattributes(fieldValue, {'numeric'}, {'scalar', ...
                        'nonnegative', 'integer'}, mfilename, 'fieldValue');
                else
                    validateattributes(fieldValue, {'char', 'string'}, ...
                        {'scalartext'}, mfilename, 'fieldValue');
                    fieldValue = char(fieldValue);
                end
            end

            if ~isempty(protocolFields)
                % Validate the protocol field names and the respective data
                % types
                validateattributes(protocolFields, {'cell'}, {'2d', 'ncols', 2}, ...
                    mfilename, 'protocolFields');
                for idx = 1:numel(protocolFields)/2
                    validateattributes(protocolFields{idx, 1}, {'char', 'string'}, ...
                        {'scalartext'}, mfilename, 'protocolFields:FieldNames');
                    protocolFields{idx, 2} = validatestring(protocolFields{idx, 2}, ...
                        pcapReaderObj.SupportedFilterTypes, mfilename, 'protocolFields:FieldTypes');
                end
            end

            % Add new row in the upper-layer protocol decoders array
            pcapReaderObj.UpperLayerDecoderCount = pcapReaderObj.UpperLayerDecoderCount + 1;
            pcapReaderObj.UpperLayerDecoders(pcapReaderObj.UpperLayerDecoderCount).ProtocolName = ...
                char(protocolName);
            pcapReaderObj.UpperLayerDecoders(pcapReaderObj.UpperLayerDecoderCount).FieldName = ...
                char(fieldName);
            pcapReaderObj.UpperLayerDecoders(pcapReaderObj.UpperLayerDecoderCount).FieldValue = ...
                fieldValue;
            pcapReaderObj.UpperLayerDecoders(pcapReaderObj.UpperLayerDecoderCount).UpperProtocolName = ...
                char(upperLayerProtocolName);
            pcapReaderObj.UpperLayerDecoders(pcapReaderObj.UpperLayerDecoderCount).UpperDecoder = upperLayerDecoder;

            % Add the decoder function handle to the list of decoders
            pcapReaderObj.DecoderCount = pcapReaderObj.DecoderCount + 1;
            pcapReaderObj.Decoders(pcapReaderObj.DecoderCount).ProtocolName = char(upperLayerProtocolName);
            pcapReaderObj.Decoders(pcapReaderObj.DecoderCount).Decoder = upperLayerDecoder;

            % Add protocol fields and the respective types
            if ~isempty(protocolFields)
                for idx = 1:numel(protocolFields)/2
                    configurePacketFilter(pcapReaderObj, upperLayerProtocolName, ...
                        protocolFields{idx, 1}, protocolFields{idx, 2});
                end
            end
        end

        function addLinkTypeDecoder(pcapReaderObj, linkType, linkName, protocolDecoder, protocolFields)
            %addLinkTypeDecoder Add a custom link layer protocol decoder to
            %PCAP file reader
            %
            %   addLinkTypeDecoder(PCAPREADEROBJ, LINKTYPE, LINKNAME,
            %   PROTOCOLDECODER) adds a link layer protocol decoder
            %   function handle, PROTOCOLDECODER, to the PCAP file reader,
            %   pcap, based on the link type in the PCAP global header,
            %   LINKTYPE. The LINKNAME input specifies a name for the link
            %   type.
            %
            %   addLinkTypeDecoder(PCAPREADEROBJ, LINKTYPE, LINKNAME,
            %   PROTOCOLDECODER, PROTOCOLFIELDS) additionally specifies
            %   protocol fields, PROTOCOLFIELDS, to filter packets based on
            %   the specified fields of the protocol decoder output
            %   structure.
            %
            %   PCAPREADEROBJ is an object of type <a href="matlab:help('pcapReader')">pcapReader</a>.
            %
            %   LINKTYPE Link type of the PCAP global header, specified as
            %   a nonnegative integer. If the link type of the PCAP file
            %   matches the specified link type value, this object
            %   functions <a href="matlab:help('pcapReader.read')">read</a> or <a href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a>
            %   calls the decoder function handle.
            %
            %   LINKNAME Link layer protocol decoder name, specified as a
            %   character vector or a string scalar. To store the decoded
            %   packet of the link layer protocol decoder, the object
            %   functions <a href="matlab:help('pcapReader.read')">read</a> or <a href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a>
            %   uses this value to create a new field in the Packet field
            %   of the output decoded protocol packet structure.
            %
            %   PROTOCOLDECODER Link layer protocol decoder that decodes
            %   the payload of the PCAP packet and returns the decoded
            %   packet with the processed length, specified as a function
            %   handle. This code shows the syntax of this argument.
            %
            %       [outputPacket, processedLength] = linkTypeDecoderFunction(payload);
            %       protocolDecoder = @linkTypeDecoderFunction
            %
            %   The linkTypeDecoderFunction is the function that decodes
            %   the payload of the PCAP packet. The outputPacket contains
            %   the decoded packet as a structure. The processedLength
            %   output is the number of decoded bytes. A negative value of
            %   the processed length indicates failed packet decoding. If
            %   packet decoding fails, the output decoded packet structure,
            %   outputPacket, is empty.
            %
            %   PROTOCOLFIELDS Protocol fields and data types, specified as
            %   a two-column cell array that indicates the protocol fields
            %   and their respective data types. Specify these fields and
            %   data types as character vectors or string scalars. The
            %   first column of the cell array specifies the field name.
            %   The second column of the cell array specifies the data type
            %   of the corresponding field name. The valid values for data
            %   type are 'numeric', 'string' and 'hexadecimal'. This value
            %   specifies the fields (of the protocol decoder output
            %   structure) on which the <a
            %   href="matlab:help('pcapReader.read')">read</a> or <a
            %   href="matlab:help('pcapReader.readAll')">pcapReader.readAll</a>
            %   object functions can specify the packetFilter input.

            % Validate the number of input arguments
            narginchk(4, 5);

            % Validate link name
            validateattributes(linkName, {'char', 'string'}, {'scalartext'}, ...
                mfilename, 'linkName');

            % Check for any existing decoders with given protocol name. If
            % the decoder is already added, then throws an error.
            [isExist, ~] = decoderWithName(pcapReaderObj, linkName);
            coder.internal.errorIf(isExist, ...
                'shared_comm_sysmod:pcapReader:UnableToAddDecoder', ...
                linkName);

            % Validate link type
            validateattributes(linkType, {'numeric'}, {'scalar', 'nonnegative', ...
                'integer'}, mfilename, 'linkType');

            % Verify that the decoder function is a function handle
            validateattributes(protocolDecoder, {'function_handle'}, {'scalar'}, ...
                mfilename, 'protocolDecoder');

            % Check for any existing decoders with given protocol name. If
            % the decoder is already added, then throws an error.
            [isExist, ~] = decoderWithValue(pcapReaderObj, linkType);
            coder.internal.errorIf(isExist, ...
                'shared_comm_sysmod:pcapReader:UnableToAddDecoder', ...
                linkName);

            if nargin == 5
                % Validate the protocol field names and the respective data
                % types
                validateattributes(protocolFields, {'cell'}, {'2d', 'ncols', 2}, ...
                    mfilename, 'protocolFields');
                for idx = 1:numel(protocolFields)/2
                    validateattributes(protocolFields{idx, 1}, {'char', 'string'}, ...
                        {'scalartext'}, mfilename, 'protocolFields:FieldNames');
                    protocolFields{idx, 2} = validatestring(protocolFields{idx, 2}, ...
                        pcapReaderObj.SupportedFilterTypes, mfilename, 'protocolFields:FieldTypes');
                end
            end

            % Add new row in the link type decoders array
            pcapReaderObj.LinkTypeDecoderCount = pcapReaderObj.LinkTypeDecoderCount + 1;
            pcapReaderObj.LinkTypeDecoders(pcapReaderObj.LinkTypeDecoderCount).ProtocolName = ...
                char(linkName);
            pcapReaderObj.LinkTypeDecoders(pcapReaderObj.LinkTypeDecoderCount).LinkType = ...
                linkType;
            pcapReaderObj.LinkTypeDecoders(pcapReaderObj.LinkTypeDecoderCount).Decoder = ...
                protocolDecoder;

            % Add the decoder function handle to the list of decoders
            pcapReaderObj.DecoderCount = pcapReaderObj.DecoderCount + 1;
            pcapReaderObj.Decoders(pcapReaderObj.DecoderCount).ProtocolName = char(linkName);
            pcapReaderObj.Decoders(pcapReaderObj.DecoderCount).Decoder = protocolDecoder;

            % Add protocol fields and the respective types
            if nargin == 5
                for idx = 1:numel(protocolFields)/2
                    configurePacketFilter(pcapReaderObj, linkName, ...
                        protocolFields{idx, 1}, protocolFields{idx, 2});
                end
            end
        end

        function protocolPackets = readAll(pcapReaderObj, packetFilter)
            %readAll Read all protocol packets from current position to the
            %end of PCAP file
            %
            %   PROTOCOLPACKETS = readAll(PCAPREADEROBJ) reads all the
            %   packets from the PCAP file and returns the decoded packets.
            %
            %   PROTOCOLPACKETS = readAll(PCAPREADEROBJ, PACKETFILTER)
            %   reads all the packets from the PCAP file and returns the
            %   decoded packets matching with the configured filters,
            %   PACKETFILTER.
            %
            %   PROTOCOLPACKETS is an array of structures and each
            %   structure contains these fields:
            %
            %   SNo          -  Serial number of the protocol packet
            %   Timestamp    -  Timestamp of the packet in the format
            %                   specified by the <a
            %                   href="matlab:help('pcapReader.OutputTimestampFormat')">OutputTimestampFormat</a> property
            %   TimestampSec -  Timestamp of the packet in seconds. To
            %                   enable this value, set <a href="matlab:help('pcapReader.OutputTimestampFormat')">OutputTimestampFormat</a>
            %                   property to "datetime".
            %   Protocol     -  Name of the link type
            %   LinkType     -  Link type specified in the PCAP global header
            %   PacketLength -  Original length of the packet transmitted
            %                   in the network, in bytes. If this value is
            %                   greater than the <a href="matlab:help('pcapReader.SnapLength')">SnapLength</a> property of the
            %                   <a href="matlab:help('pcapReader')">pcapReader</a> object, the length of the
            %                   decoded protocol packet is truncated to
            %                   contain <a href="matlab:help('pcapReader.SnapLength')">SnapLength</a> bytes.
            %   Packet       -  Decoded packet structure or packet bytes.
            %                   If the PCAP file includes Ethernet packets
            %                   or eCPRI packets encapsulated in Ethernet,
            %                   this field contains a decoded packet
            %                   structure. Otherwise, this field contains
            %                   packet bytes.
            %   RawBytes     -  Undecoded bytes because of padding or
            %                   decode error
            %
            %   If the PCAP file includes Ethernet packets, the Packet
            %   field of this argument contains a structure, eth,
            %   containing these fields:
            %
            %   SourceAddress       - Medium access control (MAC) address
            %                         of the source host in decimal bytes,
            %                         returned as a column vector
            %   DestinationAddress  - MAC address of the destination host
            %                         in decimal bytes, returned as a
            %                         column vector
            %   Type                - The type field in Ethernet header
            %                         that identifies the upper-layer
            %                         protocol, returned as a nonnegative
            %                         integer
            %   Payload             - Upper-layer protocol packet, returned
            %                         as a vector of octets where each
            %                         element is in the range [0, 255]
            %   RawBytes            - Undecoded bytes returned by the
            %                         Ethernet decoder because of padding
            %                         or decode error. If the Ethernet
            %                         decoder does not return any undecoded
            %                         bytes, this field is not present in
            %                         the eth structure.
            %
            %   If the PCAP file includes eCPRI packets encapsulated in
            %   Ethernet, the Packet field of this argument also contains a
            %   structure or a cell array of structures, ecpri, containing
            %   these fields.
            %
            %   ProtocolRevision    - Protocol version of eCPRI, specified
            %                         as a nonnegative integer
            %   Concatenation       - Concatenation indicator of eCPRI
            %                         messages, specified as 0 or 1. A
            %                         value of 0 indicates the
            %                         corresponding eCPRI message is the
            %                         last one inside the eCPRI protocol
            %                         data unit (PDU). A value of 1
            %                         indicates that another eCPRI message
            %                         follows this one within the eCPRI
            %                         PDU.
            %   MessageType         - Type of service supported by the
            %                         eCPRI message, specified as a
            %                         character vector or a string scalar
            %
            %   Based on the MessageType value, the output of the eCPRI
            %   decoder structure can contain additional fields.
            %
            %   PCAPREADEROBJ is an object of type <a href="matlab:help('pcapReader')">pcapReader</a>.
            %
            %   PACKETFILTER is the packet filter specified as a character
            %   vector or a string scalar representing the filter. The
            %   object function ignores the white spaces in the input
            %   string. The object function reads and decodes packets from
            %   a PCAP file and returns the packets that match the
            %   configured filters, PACKETFILTER.
            %
            %   You can specify this argument as a combination of one or
            %   more filters connected by logical AND (& or &&) or logical
            %   OR (| or ||). This syntax shows a typical packet filter.
            %   The object function evaluates the specified filters from
            %   left to right.
            %
            %   protocolName.FieldName RELATIONALOPERATOR value
            %
            %   The valid values for protocolName are pcap, eth, and ecpri.
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is pcap:
            %
            %   SNo             - Integer
            %   TimestampSec    - Integer
            %   PacketLength    - Integer
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is eth:
            %
            %   SourceAddress       - Character vector or string scalar
            %                         representing hexadecimal bytes
            %   DestinationAddress  - Character vector or string scalar
            %                         representing hexadecimal bytes
            %   Type                - Integer
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is ecpri:
            %
            %   ProtocolRevision    - Integer
            %   MessageType         - Character vector or string scalar
            %                         representing eCPRI message type
            %   PC_ID               - Integer
            %   SEQ_ID              - Integer
            %
            %   The valid values for RELATIONALOPERATOR are <=, >=, >, <, ==,
            %   ~=, and ~.

            % Validate number of input arguments
            narginchk(1, 2);
            if nargin == 1
                packetFilter = '';
            end

            % Read all the packets from the current position of the file
            protocolPackets = readPCAPPackets(pcapReaderObj, packetFilter, false);
        end

        function protocolPacket = read(pcapReaderObj, packetFilter)
            %READ Read next protocol packet from PCAP file
            %
            %   PROTOCOLPACKET = READ(PCAPREADEROBJ) reads the next
            %   protocol packet from the PCAP file specified by the input
            %   PCAP file reader and returns the decoded packet.
            %
            %   PROTOCOLPACKET = READ(PCAPREADEROBJ, PACKETFILTER) reads
            %   and returns only the next decoded packet that match the
            %   specified filter, PACKETFILTER.
            %
            %   PROTOCOLPACKET is a structure and contains these fields:
            %
            %   SNo          -  Serial number of the protocol packet
            %   Timestamp    -  Timestamp of the packet in the format
            %                   specified by the <a
            %                   href="matlab:help('pcapReader.OutputTimestampFormat')">OutputTimestampFormat</a> property
            %   TimestampSec -  Timestamp of the packet in seconds. To
            %                   enable this value, set <a href="matlab:help('pcapReader.OutputTimestampFormat')">OutputTimestampFormat</a>
            %                   property to "datetime".
            %   Protocol     -  Name of the link type
            %   LinkType     -  Link type specified in the PCAP global header
            %   PacketLength -  Original length of the packet transmitted
            %                   in the network, in bytes. If this value is
            %                   greater than the <a href="matlab:help('pcapReader.SnapLength')">SnapLength</a> property of the
            %                   <a href="matlab:help('pcapReader')">pcapReader</a> object, the length of the
            %                   decoded protocol packet is truncated to
            %                   contain <a href="matlab:help('pcapReader.SnapLength')">SnapLength</a> bytes.
            %   Packet       -  Decoded packet structure or packet bytes.
            %                   If the PCAP file includes Ethernet packets
            %                   or eCPRI packets encapsulated in Ethernet,
            %                   this field contains a decoded packet
            %                   structure. Otherwise, this field contains
            %                   packet bytes.
            %   RawBytes     -  Undecoded bytes because of padding or
            %                   decode error
            %
            %   If the PCAP file includes Ethernet packets, the Packet
            %   field of this argument contains a structure, eth,
            %   containing these fields:
            %
            %   SourceAddress       - Medium access control (MAC) address
            %                         of the source host in decimal bytes,
            %                         returned as a column vector
            %   DestinationAddress  - MAC address of the destination host
            %                         in decimal bytes, returned as a
            %                         column vector
            %   Type                - The type field in Ethernet header
            %                         that identifies the upper-layer
            %                         protocol, returned as a nonnegative
            %                         integer
            %   Payload             - Upper-layer protocol packet, returned
            %                         as a vector of octets where each
            %                         element is in the range [0, 255]
            %   RawBytes            - Undecoded bytes returned by the
            %                         Ethernet decoder because of padding
            %                         or decode error. If the Ethernet
            %                         decoder does not return any undecoded
            %                         bytes, this field is not present in
            %                         the eth structure.
            %
            %   If the PCAP file includes eCPRI packets encapsulated in
            %   Ethernet, the Packet field of this argument also contains a
            %   structure or a cell array of structures, ecpri, containing
            %   these fields.
            %
            %   ProtocolRevision    - Protocol version of eCPRI, specified
            %                         as a nonnegative integer
            %   Concatenation       - Concatenation indicator of eCPRI
            %                         messages, specified as 0 or 1. A
            %                         value of 0 indicates the
            %                         corresponding eCPRI message is the
            %                         last one inside the eCPRI protocol
            %                         data unit (PDU). A value of 1
            %                         indicates that another eCPRI message
            %                         follows this one within the eCPRI
            %                         PDU.
            %   MessageType         - Type of service supported by the
            %                         eCPRI message, specified as a
            %                         character vector or a string scalar
            %
            %   Based on the MessageType value, the output of the eCPRI
            %   decoder structure can contain additional fields.
            %
            %   PCAPREADEROBJ is an object of type <a href="matlab:help('pcapReader')">pcapReader</a>.
            %
            %   PACKETFILTER is the packet filter specified as a character
            %   vector or a string scalar representing the filter. The
            %   object function ignores the white spaces in the input
            %   string. The object function reads the packets matching with
            %   the specified filter, PACKETFILTER.
            %
            %   You can specify this argument as a combination of one or
            %   more filters connected by logical AND (& or &&) or logical
            %   OR (| or ||). This syntax shows a typical packet filter.
            %   The object function evaluates the specified filters from
            %   left to right.
            %
            %   protocolName.FieldName RELATIONALOPERATOR value
            %
            %   The valid values for protocolName are pcap, eth, and ecpri.
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is pcap:
            %
            %   SNo             - Integer
            %   TimestampSec    - Integer
            %   PacketLength    - Integer
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is eth:
            %
            %   SourceAddress       - Character vector or string scalar
            %                         representing hexadecimal bytes
            %   DestinationAddress  - Character vector or string scalar
            %                         representing hexadecimal bytes
            %   Type                - Integer
            %
            %   These are the valid values of FieldName and the data type
            %   of the corresponding value if protocolName is ecpri:
            %
            %   ProtocolRevision    - Integer
            %   MessageType         - Character vector or string scalar
            %                         representing eCPRI message type
            %   PC_ID               - Integer
            %   SEQ_ID              - Integer
            %
            %   The valid values for RELATIONALOPERATOR are <=, >=, >, <, ==,
            %   ~=, and ~.

            % Validate number of input arguments
            narginchk(1, 2);

            if nargin == 1
                packetFilter = '';
            end

            % Read all the packets from the current position of the file
            protocolPacket = readPCAPPackets(pcapReaderObj, packetFilter, true);
        end

        function reset(pcapReaderObj)
            %RESET Reset position of PCAP file reader to first protocol
            %packet of PCAP file
            %
            %   RESET(PCAPREADEROBJ) resets the position of the specified
            %   PCAP file reader to the first protocol packet in the PCAP
            %   file.
            %
            %   PCAPREADEROBJ is an object of type <a href="matlab:help('pcapReader')">pcapReader</a>.

            % Reset the file position to the first packet of the PCAP file,
            % after the global header of the PCAP file
            fseek(pcapReaderObj.pFID, 24, 'bof');

            % Reset the packet count
            pcapReaderObj.PacketsRead = 0;
        end
    end

    methods (Access = private)
        function protocolPackets = readPCAPPackets(pcapReaderObj, packetFilter, nextPacketFlag)
            %readPCAPPackets Read packets from the configured PCAP file

            % Initialize
            filterFlag = false;
            % Packet filter is passed as an additional input argument
            if ~isempty(packetFilter)
                validateattributes(packetFilter, {'char' ,'string'}, {'scalartext'}, ...
                    mfilename, 'packetFilter');
                filterString = char(packetFilter);
                parsedFilter = parseFilter(pcapReaderObj, filterString);
                filterFlag = true;
            end

            % Decoded packet structure with empty fields
            emptyPacket = pcapReaderObj.pEmptyPacket;
            emptyStructure = pcapReaderObj.pEmptyStructure;

            % Initialize the packets output
            protocolPackets = emptyPacket;
            % Count of packets read and returned as output
            packetCount = 0;
            % Count of packets read and used as a sequence number to the
            % packet
            packetsRead = pcapReaderObj.PacketsRead;

            % Get the decoder function handle with the decoded LinkType
            [decoderFlag, protocolName, linkTypeDecoder] = decoderWithValue(pcapReaderObj, pcapReaderObj.LinkType);
            if decoderFlag
                % Update the protocol name with the existing decoder name
                pcapReaderObj.LinkName = protocolName;
            else
                % Decoder is not added into the PCAP file reader for this
                % protocol
                pcapReaderObj.LinkName = 'Unknown';
            end

            % Update the link type and protocol name in the packet
            % structure
            emptyPacket.LinkType = pcapReaderObj.LinkType;
            emptyPacket.Protocol = pcapReaderObj.LinkName;

            % For performance improvement
            fID = pcapReaderObj.pFID;
            timestampFormat = pcapReaderObj.pTimestampFormat;
            nanoSec = pcapReaderObj.NanoSecResolution;
            valueFrom4Bytes = pcapReaderObj.pValueFrom4BytesFcn;
            packetHeaderLength = 16; % in bytes

            % Read packet header of the first packet
            packetHeader = fread(fID, packetHeaderLength);
            if (numel(packetHeader) ~= packetHeaderLength)
                % File end is reached
                coder.internal.warning('shared_comm_sysmod:pcapReader:FileEnd', pcapReaderObj.Filename);
                return;
            end

            % Read all the packets from the PCAP file until the file ends
            while (true)
                % Initialize the packet structure
                packet = emptyPacket;

                % Timestamp in seconds (4 bytes)
                tsSec = valueFrom4Bytes(packetHeader(1:4));
                % Timestamp in microseconds/nanoseconds (4 bytes)
                tsUsec = valueFrom4Bytes(packetHeader(5:8));
                if nanoSec
                    packetTimestamp = tsSec + (tsUsec*1e-9);
                else
                    packetTimestamp = tsSec + (tsUsec*1e-6);
                end

                % Length of the packet saved in the file (in bytes)
                savedLength = valueFrom4Bytes(packetHeader(9:12));
                % Actual length of the packet (in bytes)
                originalLength = valueFrom4Bytes(packetHeader(13:16));

                bytesToRead = savedLength+packetHeaderLength;
                % Read packet bytes
                readBytes = fread(fID, bytesToRead);
                bytesRead = numel(readBytes);
                if bytesRead == bytesToRead
                    packetHeader = readBytes(savedLength+1:end);
                    readBytes = readBytes(1:savedLength);
                elseif bytesRead < savedLength % No valid packet
                    break;
                elseif bytesRead < bytesToRead
                    readBytes = readBytes(1:savedLength);
                    packetHeader = zeros(1, 0);
                end

                packetsRead = packetsRead + 1;
                % Packet serial number
                packet.SNo = packetsRead;
                switch timestampFormat
                    case 0 % datetime
                        if nanoSec
                            packet.Timestamp = datetime(packetTimestamp, ...
                                'ConvertFrom',"posixtime",'Format',"dd-MMM-uuuu HH:mm:ss.SSSSSSSSS");
                        else
                            packet.Timestamp = datetime(packetTimestamp, ...
                                'ConvertFrom',"posixtime",'Format',"dd-MMM-uuuu HH:mm:ss.SSSSSS");
                        end
                        packet.TimestampSec = packetTimestamp; % Seconds
                    case 1
                        packet.Timestamp = packetTimestamp; % seconds
                    case 2
                        packet.Timestamp = packetTimestamp*1e6; % microseconds
                end
                packet.PacketLength = originalLength; % Packet length in bytes

                % Packet has the protocol decoder to decode the bytes and the
                % bytes are not truncated while writing into the PCAP file
                if decoderFlag && (savedLength == originalLength)
                    decoderOutput = decode(pcapReaderObj, readBytes, linkTypeDecoder); % Decoded packet bytes as a structure

                    % Packet is successfully decoded
                    if ~isempty(decoderOutput)
                        packet.Packet = emptyStructure;
                        packet.Packet = decoderOutput;
                    else % Packet decoding failed in the next level decoder
                        packet.RawBytes = readBytes;
                    end
                else
                    packet.Packet = readBytes; % Decoded packet bytes
                    % Add remaining raw data (not processed by the decoder)
                    % to the packet output
                    packet.RawBytes = readBytes;
                end

                % Apply the filters on the decoded packet
                if filterFlag
                    flag = evaluateFilter(pcapReaderObj, packet, parsedFilter);
                    if flag
                        packetCount = packetCount + 1;
                        protocolPackets(packetCount) = packet;
                    end
                else
                    packetCount = packetCount + 1;
                    protocolPackets(packetCount) = packet;
                end

                % No further packets in the file
                if isempty(packetHeader)
                    break;
                end

                % Read the next packet
                if nextPacketFlag && ~isempty(packetHeader) && (packetCount == 1)
                    % Move the file pointer by packet header length
                    fseek(fID, -packetHeaderLength, "cof");
                    break;
                end
            end
            pcapReaderObj.PacketsRead = packetsRead;
        end

        function configurePacketFilter(pcapReaderObj, protocolName, fieldName, fieldType)
            %configurePacketFilter Configure a filter for a protocol field
            %of specified protocol name for filtering the decoded packets
            %from the PCAP file

            % Validate protocol name
            protocolName = validatestring(protocolName, [{pcapReaderObj.Decoders.ProtocolName} 'pcap'], ...
                mfilename, 'protocolName');

            % Validate the field name
            validateattributes(fieldName, {'char', 'string'}, {'scalartext'}, mfilename, ...
                'fieldName');
            protocolIdx = find(strcmpi(protocolName, {pcapReaderObj.FilterConfig(:).ProtocolName}));
            configIdx = find(strcmpi(fieldName, {pcapReaderObj.FilterConfig(protocolIdx).FieldName}), 1);
            % Configuration for the specified protocol name and field name
            % already exists.
            coder.internal.errorIf(~isempty(configIdx), ...
                'shared_comm_sysmod:pcapReader:FailedToConfigurePacketFilter', ...
                protocolName, fieldName);

            % Validate field type
            fieldType = validatestring(fieldType, pcapReaderObj.SupportedFilterTypes, ...
                mfilename, 'fieldType');

            % Add the filter configuration
            pcapReaderObj.FilterConfigCount = pcapReaderObj.FilterConfigCount + 1;
            pcapReaderObj.FilterConfig(pcapReaderObj.FilterConfigCount).ProtocolName = char(protocolName);
            pcapReaderObj.FilterConfig(pcapReaderObj.FilterConfigCount).FieldName = char(fieldName);
            pcapReaderObj.FilterConfig(pcapReaderObj.FilterConfigCount).Type = fieldType;
        end

        function [flag, decoder] = decoderWithName(pcapReaderObj, protocolName)
            %decoderWithName Get the protocol decoder function handle with
            %the specified protocol name

            % Initialize
            flag = false;
            decoder = [];

            % Get the index from the decoder function handles array
            for idx = 1:pcapReaderObj.DecoderCount
                if strcmpi(pcapReaderObj.Decoders(idx).ProtocolName, protocolName)
                    decoder = pcapReaderObj.Decoders(idx).Decoder;
                    flag = true;
                    break;
                end
            end
        end

        function readGlobalHeader(pcapReaderObj)
            %readGlobalHeader Read global header of the PCAP file

            % Read global header bytes from the PCAP file
            globalHeaderSize = 24; % in bytes
            globalHeader = fread(pcapReaderObj.pFID, globalHeaderSize);
            coder.internal.errorIf((numel(globalHeader) ~= globalHeaderSize), ...
                'shared_comm_sysmod:pcapReader:InvalidPCAPFormat', pcapReaderObj.Filename);

            % Magic number (4 bytes), defines the byte-ordering of the PCAP file
            magicNumber = reshape(dec2hex(globalHeader(1:4))', 1, []);
            swapFlag = false;

            % Validate the PCAP file byte-order
            switch magicNumber
                case 'A1B2C3D4'
                    swapFlag = false;
                    pcapReaderObj.NanoSecResolution = false;
                case 'D4C3B2A1'
                    swapFlag = true;
                    pcapReaderObj.NanoSecResolution = false;
                case 'A1B23C4D'
                    swapFlag = false;
                    pcapReaderObj.NanoSecResolution = true;
                case '4D3CB2A1'
                    swapFlag = true;
                    pcapReaderObj.NanoSecResolution = true;
                otherwise
                    coder.internal.error('shared_comm_sysmod:pcapReader:InvalidMagicNumber', ...
                        pcapReaderObj.Filename);
            end
            pcapReaderObj.MagicNumber = magicNumber;

            if swapFlag % Bytes are swapped
                % Anonymous functions for converting the 4-bytes into the
                % equivalent decimal value
                pcapReaderObj.pValueFrom4BytesFcn = @(bytes) bytes(4)*16777216 + bytes(3)*65536 + bytes(2)*256 + bytes(1);

                % Anonymous functions for converting the 2-bytes into the
                % equivalent decimal value
                pcapReaderObj.pValueFrom2BytesFcn = @(bytes) bytes(2)*256 + bytes(1);
            else % Bytes are not swapped
                % Anonymous functions for converting the 4-bytes bytes into
                % value
                pcapReaderObj.pValueFrom4BytesFcn = @(bytes) bytes(1)*16777216 + bytes(2)*65536 + bytes(3)*256 + bytes(4);

                % Anonymous functions for converting the 2-bytes into value
                pcapReaderObj.pValueFrom2BytesFcn = @(bytes) bytes(1)*256 + bytes(2);
            end

            % Major version of PCAP file format (2 bytes)
            majorVersion = pcapReaderObj.pValueFrom2BytesFcn(globalHeader(5:6));
            % Minor version of PCAP file format (2 bytes)
            minorVersion = pcapReaderObj.pValueFrom2BytesFcn(globalHeader(7:8));
            % Validate the version of the PCAP file format
            coder.internal.errorIf(((majorVersion ~= 2) || (minorVersion ~= 4)), ...
                'shared_comm_sysmod:pcapReader:UnsupportedFileVersion', ...
                majorVersion, minorVersion);
            pcapReaderObj.MajorVersion = majorVersion;
            pcapReaderObj.MinorVersion = minorVersion;

            % Maximum length of packet logged in this file (4 bytes)
            pcapReaderObj.SnapLength = pcapReaderObj.pValueFrom4BytesFcn(globalHeader(17:20));
            % Link type or protocol number (4 bytes)
            pcapReaderObj.LinkType = pcapReaderObj.pValueFrom4BytesFcn(globalHeader(21:24));
        end

        function [hasField, fieldValue] = getStructField(pcapReaderObj, inStruct, fieldName)
            %getStructField Get the specified field value from the given input
            %structure

            % Initialize
            hasField = false;
            fieldValue = [];

            % Return the value, if the field is present in the structure
            if isfield(inStruct, fieldName)
                hasField = 1;
                fieldValue = inStruct.(fieldName);
                return;
            else
                % Get the field names of the input structure
                fNames = fieldnames(inStruct);
                for idx = 1:length(fNames)
                    % Traverse into the nested structure to get the
                    % specified field value
                    if isstruct(inStruct.(fNames{idx}))
                        [hasField, fieldValue] = getStructField(pcapReaderObj, inStruct.(fNames{idx}), fieldName);
                        if hasField
                            return;
                        end
                    end
                end
            end
        end

        function [flag, linkName, decoder] = decoderWithValue(pcapReaderObj, linkType)
            %decoderWithValue Get the protocol decoder function handle with
            %the specified link type

            % Initialize
            flag = false;
            decoder = [];
            linkName = blanks(0);

            % Get the index from the link type decoder function handles
            % array
            for idx = 1:pcapReaderObj.LinkTypeDecoderCount
                if (pcapReaderObj.LinkTypeDecoders(idx).LinkType == linkType)
                    flag = true;
                    decoder = pcapReaderObj.LinkTypeDecoders(idx).Decoder;
                    linkName = pcapReaderObj.LinkTypeDecoders(idx).ProtocolName;
                    break;
                end
            end
        end

        function [protocolName, fieldName, fieldValue] = validateFilter(pcapReaderObj, protocolName, fieldName, fieldValue)
            %validateFilter Validate the configured filter as a tuple of protocol
            %name, field name and the corresponding value

            % Validate protocol name
            filterConfig = pcapReaderObj.FilterConfig;
            protocolName = validatestring(protocolName, unique({filterConfig(:).ProtocolName}),...
                mfilename, 'protocolName');

            % Get the filter configuration of the specified protocol
            configIdxs = find(strcmpi(protocolName, {filterConfig(:).ProtocolName}));
            validFieldNames = {filterConfig(configIdxs).FieldName};

            % Validate protocol field name
            fieldName = validatestring(fieldName, validFieldNames, mfilename, 'fieldName');

            % Get the data type of given protocol field
            for typeIdx = 1:numel(configIdxs)
                if strcmpi(fieldName, validFieldNames{typeIdx})
                    filterType = filterConfig(configIdxs(typeIdx)).Type;
                    break;
                end
            end

            % Validate and evaluate the protocol field value based on its
            % data type
            if nargin > 3
                varName = [protocolName '.' fieldName];
                switch filterType
                    case 'numeric' % Non-negative integer value
                        fieldValue = str2double(fieldValue);
                        validateattributes(fieldValue, {'numeric'}, {'scalar'}, ...
                            mfilename, varName);
                    case 'hexadecimal' % Hexadecimal octets
                        validateattributes(fieldValue, {'char'}, ...
                            {'vector'}, mfilename, varName);
                        comm_sysmod.internal.validateHexOctets(fieldValue, varName);
                        % Convert the hexadecimal field value into decimal octets
                        fieldValue = hex2dec(reshape(fieldValue, 2, [])');
                    case 'string' % String or character vector
                        validateattributes(fieldValue, {'char', 'string'}, ...
                            {'scalartext'}, mfilename, varName);
                end
            end
        end

        function delete(pcapReaderObj)
            %delete Close the opened PCAP file

            if pcapReaderObj.pFID >= 0
                fclose(pcapReaderObj.pFID);
                pcapReaderObj.pFID = -1;
            end
        end

        function flag = evaluateFilter(pcapReaderObj, decodedPacket, parsedFilter)
            %evaluateFilter Apply the configured filter on the decoded
            %packet and evaluate whether the packet should be filtered or
            %not. Return true, if the packet is matched with the configured
            %filter and return to the output.

            % Initialize
            flag = true;
            tupleFlag = true;
            logExpression = '&&';
            idx = 1;

            % Apply the filter
            while(idx < numel(parsedFilter))
                if strcmp(parsedFilter{idx}, '~')
                    % Get the filter tuple (protocol name and field name)
                    fieldExpression = parsedFilter{idx};
                    idx = idx + 1;
                    protocolName = parsedFilter{idx};
                    idx = idx + 1;
                    fieldName = parsedFilter{idx};
                    idx = idx + 1;
                    fieldValue = 0;
                else
                    % Get the filter tuple (protocol name, field name and field value)
                    protocolName = parsedFilter{idx};
                    idx = idx + 1;
                    fieldName = parsedFilter{idx};
                    idx = idx + 1;
                    fieldExpression = parsedFilter{idx};
                    idx = idx + 1;
                    if ~strcmpi(fieldExpression, '~')
                        fieldValue = parsedFilter{idx};
                        idx = idx + 1;
                    end
                end

                if strcmpi(protocolName, 'pcap')
                    % Check the presence of PCAP header field
                    [~, actualValue] = getStructField(pcapReaderObj, decodedPacket, fieldName);
                else
                    % Check the protocol presence in the decoded packet
                    [fieldFlag, protocolPacket] = getStructField(pcapReaderObj, decodedPacket, protocolName);
                    if ~fieldFlag
                        flag = false;
                        break;
                    else
                        if iscell(protocolPacket)
                            numElements = numel(protocolPacket);
                            fieldFlag = true(1, numElements);
                            actualValue = cell(1, numElements);
                            for elementIdx = 1:numElements
                                % Check the presence of protocol field
                                [fieldFlag(elementIdx), actualValue{elementIdx}] = getStructField(pcapReaderObj, protocolPacket{elementIdx}, fieldName);
                            end
                        else
                            % Check the presence of protocol field
                            [fieldFlag, actualValue] = getStructField(pcapReaderObj, protocolPacket, fieldName);
                        end
                        if ~all(fieldFlag)
                            flag = false;
                            break;
                        end
                    end
                end
                % When the decoded packet contains more than
                % one field of the specified filter, compare
                % all the fields by converting the cell array
                % into vector.
                if iscell(actualValue) && ~ischar(fieldValue)
                    actualValue = cell2mat(actualValue);
                end
                % Evaluate the filter expression
                switch fieldExpression
                    case '=='
                        if ischar(fieldValue)
                            tupleFlag = strcmpi(fieldValue, actualValue);
                        else
                            if numel(fieldValue) > 1
                                tupleFlag = isequal(actualValue, fieldValue);
                            else
                                tupleFlag = (actualValue == fieldValue);
                            end
                        end
                    case '~='
                        if ischar(fieldValue)
                            tupleFlag = ~strcmpi(fieldValue, actualValue);
                        else
                            tupleFlag = (actualValue ~= fieldValue);
                        end
                    case '<='
                        tupleFlag = (actualValue <= fieldValue);
                    case '>='
                        tupleFlag = (actualValue >= fieldValue);
                    case '<'
                        tupleFlag = (actualValue < fieldValue);
                    case '>'
                        tupleFlag = (actualValue > fieldValue);
                    case '~'
                        tupleFlag = (~actualValue);
                end
                % Evaluate the filter with the specified logical expression
                switch logExpression
                    case {'&&', '&'}
                        flag = flag && all(tupleFlag);
                    case {'||', '|'}
                        flag = flag || all(tupleFlag);
                end
                % Get the immediate logical expression from the filter string
                if (idx < numel(parsedFilter))
                    logExpression = parsedFilter{idx};
                    idx = idx + 1;
                end
            end
        end

        function packet = decode(pcapReaderObj, payload, decoder)
            %decode Decode the payload by using the given link type
            %decoder, DECODER, and upper-layer decoders and return the
            %decoded packet as structure.

            % Initialize
            decoderFlag = true;
            packet = pcapReaderObj.pEmptyStructure;
            % Protocol name
            protocolName = pcapReaderObj.LinkName;
            % Previous protocol name
            previousProtocolName = '';
            % Payload flag
            hasPayload = ~isempty(payload);

            % Parse the payload by passing to all the relevant protocol
            % decoders
            while (decoderFlag) && (hasPayload)
                % Reset the decoder and payload outputs
                decoderOutputs = cell(1, 0);

                % If the decoder output is cell, then there is more than
                % one structure in the output.
                if iscell(payload)
                    numInputs = numel(payload);
                    for inputIdx = 1:numInputs
                        % Input payload and the respective decoder
                        inputPayload = payload{inputIdx};
                        rawBytes = zeros(1, 0);
                        % Decode the payload by passing to the protocol decoders
                        [decoderOutput, processedLength] = decoder(inputPayload);

                        if processedLength > 0 % Packet is successfully decoded
                            % Add remaining raw data (not processed by the
                            % decoder) to the packet output
                            if (numel(inputPayload) > processedLength)
                                rawBytes = inputPayload(processedLength+1:end);
                            end
                            % Add the decoder output to the packet
                            if isfield(packet, protocolName)
                                if iscell(packet.(protocolName)) || iscell(decoderOutput)
                                    packet.(protocolName) = [packet.(protocolName) decoderOutput];
                                else
                                    packet.(protocolName) = {packet.(protocolName) decoderOutput};
                                end
                            else
                                packet.(protocolName) = decoderOutput;
                            end
                            decoderOutputs = [decoderOutputs decoderOutput]; %#ok<AGROW>
                        else % Packet decoding failed in the next level decoder
                            rawBytes = inputPayload;
                        end

                        % Add the raw bytes into the packet
                        if ~isempty(rawBytes)
                            if isfield(packet.(previousProtocolName){1}, 'RawBytes')
                                packet.(previousProtocolName){1}.RawBytes = [packet.(previousProtocolName){1}.RawBytes rawBytes];
                            else
                                packet.(previousProtocolName){1}.RawBytes = {rawBytes};
                            end
                        end
                    end
                else
                    % Decode the payload by passing to the protocol decoders
                    [decoderOutput, processedLength] = decoder(payload);
                    rawBytes = zeros(1, 0);
                    if processedLength > 0 % Packet is successfully decoded
                        % Add remaining raw data (not processed by the
                        % decoder) to the packet output
                        if (numel(payload) > processedLength)
                            rawBytes = payload(processedLength+1:end);
                        end
                        % Add the decoder output to the packet
                        packet.(protocolName) = decoderOutput;
                        decoderOutputs = [decoderOutputs decoderOutput]; %#ok<AGROW> 
                    else % Packet decoding failed in the next level decoder
                        rawBytes = payload;
                    end
                    % Packet decoding is failed
                    if ~isempty(rawBytes) > 0
                        % Packet decoding is failed at the link type decoder
                        if isempty(previousProtocolName)
                            packet = [];
                            break;
                        else
                            % Add the raw bytes into the packet
                            packet.(previousProtocolName).RawBytes = rawBytes;
                        end
                    end
                end
                % Fetch the next-level payloads from the decoder output
                [hasPayload, payload] = nextPayload(pcapReaderObj, decoderOutputs);

                % Check for the presence of the payload field to be
                % processed by the next level decoders
                if hasPayload
                    % Fetch the next upper-layer decoder to process the
                    % payload of the previous decoder
                    [decoder, upperProtocolName] = nextUpperDecoder(pcapReaderObj, protocolName, decoderOutputs{1});
                    if ~isempty(decoder)
                        decoderFlag = true;
                        previousProtocolName = protocolName;
                        protocolName = upperProtocolName;
                    else
                        decoderFlag = false;
                    end
                end
            end
        end

        function [decoder, upperProtocolName] = nextUpperDecoder(pcapReaderObj, protocolName, decoderOutput)
            %nextUpperDecoder Get the next upper-layer decoder for the given
            %decoder output structure

            % Initialize
            decoder = [];
            upperProtocolName = '';

            % Get all the next level decoders available from the given
            % protocol name
            nextDecoders = pcapReaderObj.UpperLayerDecoders;
            decoderIdxs = find(strcmpi(protocolName, {nextDecoders(:).ProtocolName}));
            fieldNames = fieldnames(decoderOutput);

            for idx = 1:numel(decoderIdxs)
                nextDecoder = nextDecoders(decoderIdxs(idx));
                % Get the next level decoder function handle
                if any(strcmp(nextDecoder.FieldName, fieldNames)) && ...
                        isequal(nextDecoder.FieldValue, decoderOutput.(nextDecoder.FieldName))
                    decoder = nextDecoder.UpperDecoder;
                    upperProtocolName = nextDecoder.UpperProtocolName;
                    break;
                end
            end
        end

        function [hasPayload, payload] = nextPayload(~, decoderOutputs)
            %nextPayload Get the payload from the decoder output to be
            %processed by the next-level decoder
        
            % Initialize
            hasPayload = false;
            payload = cell(1, 0);
            payloadIdx = 0;

            % Fetch the payload from decoder outputs
            for idx = 1:numel(decoderOutputs)
                if isfield(decoderOutputs{idx}, 'Payload')
                    hasPayload = true;
                    payloadIdx = payloadIdx + 1;
                    payload{payloadIdx} = decoderOutputs{idx}.Payload;
                end
            end
            if hasPayload && payloadIdx == 1
                payload = payload{1};
            end
        end
        
        function addBuiltInDecoders(pcapReaderObj)
            %addBuiltInDecoders Add built-in decoders supported by the PCAP
            %file reader

            % Add the configuration for applying the filters on the
            % PCAP packet header.

            % Add configuration for the packet length as numeric. This
            % specifies that the packet length should be in numeric format
            % when applying the filters on it.
            configurePacketFilter(pcapReaderObj, 'pcap', 'PacketLength', 'numeric');
            % Add configuration for the serial number as numeric. This
            % specifies that the serial number should be in numeric format
            % when applying the filters on it.
            configurePacketFilter(pcapReaderObj, 'pcap', 'SNo', 'numeric');
            % Add configuration for the packet timestamp as numeric. This
            % specifies that the packet timestamp should be in numeric
            % format when applying the filters on it.
            configurePacketFilter(pcapReaderObj, 'pcap', 'TimestampSec', 'numeric');

            % Add the configuration for applying the filters on the
            % Ethernet packet fields.
            %
            % Add configuration for the Ethernet source address as
            % hexadecimal number. This specifies that the Ethernet source
            % address should be in hexadecimal format when applying the
            % filters on it.
            %
            % Add configuration for the Ethernet destination address as
            % hexadecimal number. This specifies that the Ethernet
            % destination address should be in hexadecimal format when
            % applying the filters on it.
            %
            % Add configuration for the Ethernet type as numeric. This
            % specifies that the Ethernet type should be in numeric format
            % when applying the filters on it.
            ethProtocolFields = {'SourceAddress', 'hexadecimal'; ...
                'DestinationAddress', 'hexadecimal'; ...
                'Type', 'numeric'};

            % Add Ethernet decoder. It will be invoked when the
            % pcap.LinkType is 1.
            addLinkTypeDecoder(pcapReaderObj, 1, 'eth', @comm_sysmod.internal.decodeEthernetPacket, ...
                ethProtocolFields);

            % Add the configuration for applying the filters on the
            % Ethernet packet fields.
            %
            % Add configuration for the eCPRI message type as string. This
            % specifies that the eCPRI message type should be in string
            % scalar or character vector format when applying the filters
            % on it.
            %
            % Add configuration for the eCPRI protocol revision as numeric.
            % This specifies that the eCPRI protocol revision should be in
            % numeric format when applying the filters on it.
            %
            % Add configuration for the eCPRI PC_ID as numeric. This
            % specifies that the eCPRI PC_ID should be in numeric format
            % when applying the filters on it.
            %
            % Add configuration for the eCPRI SEQ_ID as numeric. This
            % specifies that the eCPRI SEQ_ID should be in numeric format
            % when applying the filters on it.
            ecpriProtocolFields = {'MessageType', 'string'; ...
                'ProtocolRevision', 'numeric'; ...
                'PC_ID', 'numeric'; ...
                'SEQ_ID', 'numeric'};

            % Add eCPRI decoder. It will be invoked when the eth.Type is 44798.
            addUpperLayerDecoder(pcapReaderObj, 'eth', 'Type', 44798, 'ecpri', @comm_sysmod.internal.decodeECPRIPacket, ...
                ecpriProtocolFields);
        end

        function parsedFilter = parseFilter(pcapReaderObj, filterString)
            %parseFilter Parse the input filter string into cell-array by
            %dividing the string into tuples.

            % Allowed characters for the input filter are: A-Z, a-z, &, |, <,
            % >, =, ~, . and space ( ).
            allowedChars = ['a':'z' 'A':'Z' '0':'9' '&|<>=~. _'];
            coder.internal.errorIf(~all(ismember(filterString, allowedChars)), ...
                'shared_comm_sysmod:pcapReader:InvalidFilter', filterString);

            % Remove the empty spaces in the input filter string
            filter = erase(filterString, ' ');

            % Split the given input filter string
            [filterTuples, logicalOperators] = strsplit(filter, {'&&', '|', '||', '&'});

            % Initialize the parsed filter
            parsedFilter = {};

            % Validate each filter tuple
            for idx = 1:numel(filterTuples)
                % Split the tuple as field name, logical operator and value
                [filterTuple, logOperator] = strsplit(filterTuples{idx}, ...
                    {'<=', '>=', '>', '<', '==', '~=', '~'});
                % The tuple must contain either field or field and value
                coder.internal.errorIf((numel(filterTuple) ~= 2), ...
                    'shared_comm_sysmod:pcapReader:InvalidFilterExpression', filterString);

                if strcmp(logOperator, '~')
                    % Split the filter tuple as protocol name and the filed name
                    protocolField = strsplit(filterTuple{2}, '.');
                    % The "protocolField" must contain protocol and field names
                    coder.internal.errorIf((numel(protocolField) ~= 2), ...
                        'shared_comm_sysmod:pcapReader:InvalidFilterExpression', filterString);

                    % Validate the protocol name and field name
                    protocolName = protocolField{1};
                    fieldName = protocolField{2};
                    [protocolName, fieldName] = validateFilter(pcapReaderObj, protocolName, fieldName);

                    % Form the filter as a cell array of strings
                    parsedFilter = [parsedFilter logOperator protocolName fieldName]; %#ok<AGROW>
                else
                    % The tuple must not contain any empty fields
                    coder.internal.errorIf(any(cellfun(@isempty, filterTuple)), ...
                        'shared_comm_sysmod:pcapReader:InvalidFilterExpression', filterString);
                    % Split the filter tuple as protocol name and the filed name
                    protocolField = strsplit(filterTuple{1}, '.');
                    % The "protocolField" must contain protocol and field names
                    coder.internal.errorIf((numel(protocolField) ~= 2), ...
                        'shared_comm_sysmod:pcapReader:InvalidFilterExpression', filterString);
                    protocolFieldValue = filterTuple{2};

                    % Validate the protocol name and field name
                    protocolName = protocolField{1};
                    fieldName = protocolField{2};
                    [protocolName, fieldName, protocolFieldValue] = ...
                        validateFilter(pcapReaderObj, protocolName, fieldName, protocolFieldValue);

                    % Form the filter as a cell array of strings
                    parsedFilter = [parsedFilter protocolName fieldName logOperator protocolFieldValue]; %#ok<AGROW>
                end
                if idx ~= numel(filterTuples)
                    % Append the parsed filter with logical operator
                    parsedFilter = [parsedFilter logicalOperators{idx}]; %#ok<AGROW>
                end
            end
        end
    end
end

% LocalWords:  PCAP pcap Ethernet ecpri eCPRI pcapng