classdef HelperWLANPacketWriter < handle & HelperPCAPUtils
%HelperWLANPacketWriter Create a PCAP/PCAPNG file writer handle object
%
%   OBJ = HelperWLANPacketWriter creates a packet capture (PCAP)/packet
%   capture next generation (PCAPNG) file writer handle object, OBJ, that
%   writes the WLAN MAC packets into a PCAP/PCAPNG format file.
%
%   OBJ = HelperWLANPacketWriter(Name1, Value1, ..., NameN, ValueN)
%   specifies additional name-value pair arguments described below. When a
%   name-value pair is not specified, its default value is used.
%
%   FileName              - Name of the file. It is specified as a
%                           character vector or a string. The default file
%                           name is 'capture'.
%
%   FileExtension         - Extension of the file. It can be one of the
%                           'pcap' | 'pcapng'. The default extension is
%                           'pcap'.
%
%   FileComment           - Comment for the file. It is specified as a
%                           character vector or a string. The default value
%                           is an empty character.
%
%   Interface             - The name of the device used to capture data. It
%                           is specified as a character vector or a string.
%                           The default value is 'WLAN'.
%                           Examples: 'eth0',
%                                     '\Device\NPF_{AD1CE675-96D0-47C5-ADD0-2504B9126B68}'.
%
%   ByteOrder             - Specifies the sequential order in which bytes
%                           are arranged into larger numerical values. It
%                           can be one of the 'little-endian'|'big-endian'.
%                           The default value is 'little-endian'.
%
%   RadiotapFlag          - Flag represents the presence of radiotap
%                           header
%
%   PCAPWriter            - Object of type <a
%                           href="matlab:help('HelperPCAPNGWriter')">HelperPCAPNGWriter</a> | <a href="matlab:help('HelperPCAPWriter')">HelperPCAPWriter</a>.
%                           This value is used when multiple protocol
%                           frames are to be written in the same file. When
%                           PCAPWriter is passed, the arguments FileName,
%                           FileExtension, FileComment and ByteOrder are
%                           ignored. These values are taken from the
%                           PCAPWriter object.
%
%   HelperWLANPacketWriter methods:
%
%   write                 - Write MAC frame into PCAP/PCAPNG format
%
%   % Example 1:
%   %  Write WLAN packet into PCAP format file
%
%   % Create a WLAN object with file name as sample.pcap
%   pcapObj = HelperWLANPacketWriter('FileName', 'sample', ...
%                                      'FileExtension', 'pcap');
%
%   % Create a WLAN MAC packet of type QoS Data
%   macConfig = wlanMACFrameConfig('FrameType', 'QoS Data');
%   payload ='00576000103afffe80';
%   MPDU = hex2dec(wlanMACFrame(payload, macConfig));
%
%   % Write the MAC protocol data unit (MPDU)
%   write(pcapObj, MPDU);
%
%
%   % Example 2:
%   %  Write WLAN packet into PCAPNG format file
%
%   % Create a WLAN object with file name as sample.pcapng
%   pcapObj = HelperWLANPacketWriter('FileName', 'sample', ...
%                                      'FileExtension', 'pcapng');
%
%   % Create a WLAN MAC packet of type QoS Data
%   macConfig = wlanMACFrameConfig('FrameType', 'QoS Data');
%   payload = '00576000103afffe80';
%   MPDU = hex2dec(wlanMACFrame(payload, macConfig));
%
%   % Write the MPDU
%   write(pcapObj,MPDU);
%
%
%   % Example 3:
%   %  Write 3 WLAN packets into PCAPNG format file with timestamp,
%   %  radiotap information, file comments, and packet comments
%
%   % Create a WLAN object with file name as sample.pcapng
%   pcapObj = HelperWLANPacketWriter('FileName', 'sample', 'FileExtension', ...
%                         'pcapng','FileComment', 'This is a sample file', ...
%                         'RadiotapFlag', true);
%
%   % Create a WLAN MAC packet of type QoS Data
%   macConfig = wlanMACFrameConfig('FrameType', 'QoS Data');
%   payload = '00576000103afffe80';
%   MPDU = hex2dec(wlanMACFrame(payload, macConfig));
%
%   % Write 3 packets into 'sample.pcapng' file
%   radiotapBytes = createRadiotapHeader('Non-HT', 0, 20);
%   write(pcapObj, MPDU, 'Timestamp', 0, 'Radiotap', radiotapBytes, ...
%             'Comment', 'This is the first packet');
%   radiotapBytes = createRadiotapHeader('VHT', 0, 20);
%   write(pcapObj, MPDU, 'Timestamp', 124800, 'Radiotap', radiotapBytes, ...
%             'Comment', 'This is the second packet');
%   radiotapBytes = createRadiotapHeader('HT-Mixed', 0, 20);
%   write(pcapObj, MPDU, 'Timestamp', 129100, 'Radiotap', radiotapBytes, ...
%             'Comment', 'This is the third packet');
%
%   See also HelperPCAPWriter, HelperPCAPNGWriter, HelperPCAPUtils
%
%   Reference:
%       https://wiki.wireshark.org/Development/LibpcapFileFormat (pcap
%       format information)
%       https://www.tcpdump.org/linktypes.html (linktypes information)
%       https://www.radiotap.org/fields/defined (radiotap information)

%   Copyright 2019 The MathWorks, Inc.

%#codegen

    properties (Access = private)
        %InterfaceID Interface identifier
        %   Unique identifier for an interface
        InterfaceID = 0

        %IsPCAPNG PCAPNG file format flag
        %   Set this property to true to indicate that the file format is
        %   PCAPNG. The default value is false.
        IsPCAPNG (1, 1) logical = false;

        %PCAPPacketWriter PCAP packet writer
        %   HelperPCAPWriter handle class object
        PCAPPacketWriter

        %PCAPNGPacketWriter PCAPNG packet writer object
        %   HelperPCAPNGWriter handle class object
        PCAPNGPacketWriter
    end

    properties (GetAccess = public, SetAccess = private)
        %PCAPWriter Packet writer object
        %   Passed as a name-value pair so that multiple protocol frames
        %   can be written into a file.
        PCAPWriter

        %FileName Name of the file
        %   It is specified as a character vector or a string. The default
        %   file name is 'capture'.
        FileName = 'capture'

        %FileExtension Extension of the file
        %   It can be one of the 'pcap' | 'pcapng'. The default extension
        %   is 'pcap'.
        FileExtension = 'pcap'

        %FileComment Comment for the file
        %   It is specified as a character vector or a string. The default
        %   value is an empty character.
        FileComment = blanks(0)

        %Interface Name of the device used to capture data
        %   It is specified as a character vector or a string. The default
        %   value is 'WLAN'.
        Interface = 'WLAN'

        %ByteOrder Order of bytes
        %   Specifies the sequential order in which bytes are arranged into
        %   larger numerical values
        ByteOrder = 'little-endian'

        %RadiotapFlag Radiotap flag
        %   Set this property to true to indicate that the radiotap header
        %   is present. The default value is false.
        RadiotapFlag (1, 1) logical = false
    end

    properties (Constant, Hidden)
        %LinkType Link type
        %   Unique identifier for WLAN MAC packet with out radiotap header
        LinkType = 105

        %LinkTypeRadiotap  Link type radiotap
        %   Unique identifier for WLAN MAC packet with radiotap header
        RadiotapLinkType = 127
    end

    methods
        function obj = HelperWLANPacketWriter(varargin)
            %HelperWLANPacketWriter Create a packet writer configuration
            %object

            % Name-value pair check
            if mod(nargin,2)
                error('Incorrect number of input arguments. Number of input arguments must be even.');
            end

            % Initialize packet writer flag to zero
            packetWriterFlag = 0;

            % Initialize for codegen support
            obj.PCAPPacketWriter = HelperPCAPWriter(varargin{:});
            obj.PCAPNGPacketWriter = HelperPCAPNGWriter(varargin{:});

            % Apply name-value pairs
            for idx = 1:2:nargin-1
                if strcmp(varargin{idx}, 'PCAPWriter')
                    packetWriterFlag = 1;
                end
                obj.(varargin{idx}) = varargin{idx+1};
            end

            % Set link type based on radiotap flag
            if obj.RadiotapFlag
                linkType = obj.RadiotapLinkType;
            else
                linkType = obj.LinkType;
            end

            % Check if 'PCAPWriter' object is passed as a name-value pair
            if packetWriterFlag
                if isa(obj.PCAPWriter, 'HelperPCAPWriter')
                    obj.FileExtension = 'pcap';
                    obj.PCAPPacketWriter = obj.PCAPWriter;
                    if obj.PCAPPacketWriter.IsHeader
                        error('Writing packets of multiple interfaces not supported in PCAP file format.');
                    end
                else
                    obj.FileExtension = 'pcapng';
                    obj.PCAPNGPacketWriter = obj.PCAPWriter;
                    obj.IsPCAPNG = true;
                end
            else
                if strcmp(obj.FileExtension, 'pcap')
                    if ~isempty(obj.FileComment)
                        disp('WARN: Writing file comments not supported in PCAP file format.');
                    end
                else
                    obj.IsPCAPNG = true;
                end
            end

            % Initialize the properties
            if obj.IsPCAPNG
                obj.FileName = obj.PCAPNGPacketWriter.FileName;
                obj.ByteOrder = obj.PCAPNGPacketWriter.ByteOrder;
                obj.FileComment = obj.PCAPNGPacketWriter.FileComment;
            else
                obj.FileName = obj.PCAPPacketWriter.FileName;
                obj.ByteOrder = obj.PCAPPacketWriter.ByteOrder;
            end

            % Write interface description block if the file format is of
            % type 'pcapng' and global header block if the file format is
            % of type 'pcap'
            if strcmp(obj.FileExtension, 'pcapng')
                obj.InterfaceID = obj.PCAPNGPacketWriter.writeInterfaceDescriptionBlock(linkType, obj.Interface);
            else
                obj.PCAPPacketWriter.writeGlobalHeader(linkType);
            end
        end

        function set.FileExtension(obj, value)
            validatestring(value, {'pcap', 'pcapng'}, ...
                mfilename, 'FileExtension');
            obj.FileExtension = value;
        end

        function set.Interface(obj, value)
            validateattributes(value, {'char', 'string'}, {'row'}, ...
                mfilename, 'FileComment');
            obj.Interface = value;
        end

        function set.PCAPWriter(obj, value)
            validateattributes(value, {'HelperPCAPNGWriter', 'HelperPCAPWriter'}, ...
                {'row'}, mfilename, 'PCAPWriter object');
            obj.PCAPWriter = value;
        end

        function write(obj, mpdu, varargin)
            %write Write MAC frame into PCAP/PCAPNG format
            %
            %   write(OBJ, MPDU) writes MPDU into PCAP/PCAPNG format
            %
            %   MPDU      - MPDU specified as one of the following types:
            %                 - A character vector representing octets in
            %                   hexadecimal format.
            %                 - A string scalar representing octets in
            %                   hexadecimal format.
            %                 - A numeric vector, where each element is in
            %                   the range of [0 - 255] inclusive,
            %                   representing octets in decimal format.
            %                 - An n-by-2 character array, where each row
            %                   represents an octet in hexadecimal format.
            %
            %   write(OBJ, MPDU, Name1, Value1, ..., NameN, ValueN)
            %   specifies additional name-value pair arguments described
            %   below. You can specify additional name-value pair arguments
            %   in any order as (Name1, Value1, ..., NameN, ValueN). When a
            %   name-value pair is not specified, the default value of the
            %   arguments is used.
            %
            %   Radiotap  - Radiotap is specified as one of the following
            %               types:
            %                 - A character vector representing octets in
            %                   hexadecimal format
            %                 - A string scalar representing octets in
            %                   hexadecimal format
            %                 - A numeric vector, where each element is in
            %                   the range of [0 - 255] inclusive,
            %                   representing octets in decimal format
            %                 - An n-by-2 character array, where each row
            %                   represents an octet in hexadecimal format
            %   Timestamp - Packet arrival time in microseconds. It is
            %               specified as a double value. The default
            %               value is zero.
            %   Comment   - Packet comment in PCAPNG format. It is
            %               specified as a character vector or a string.
            %               The default value is an empty character.

            % Validate packet and return octets in decimal format
            packetData = obj.validatePayloadFormat(mpdu);
            % Initialize an empty structure
            pcap = struct;
            % Initialize empty radiotap header for codegen
            radiotapHeader = [];

            % Name-value pair check
            if mod(nargin-2, 2)
                error('Incorrect number of input arguments. Number of input arguments must be even.');
            end

            % Apply name-value pairs
            for i = 1:2:nargin-2
                pcap.(varargin{i}) = varargin{i+1};
            end

            % Check if 'radiotap' information is passed as name-value pair
            if isfield(pcap, 'Radiotap')
                % Validate radiotap and return octets in decimal format
                radiotapHeader = obj.validatePayloadFormat(pcap.Radiotap);

                % Calculate the radiotap length from 3rd,4th bits
                radiotapLength = radiotapHeader(3)+radiotapHeader(4)*256;

                if numel(radiotapHeader) ~= radiotapLength
                    error('Invalid length of "Radiotap". Length field of "Radiotap" does not match with the total length of "Radiotap".');
                end
                if ~obj.RadiotapFlag
                    disp('WARN : Ignoring the "Radiotap" because value of "RadiotapFlag" is set to false.');
                    radiotapHeader = [];
                end
            else
                if obj.RadiotapFlag
                    error('Value of "Radiotap" is expected because "RadiotapFlag" is set to true.');
                end
            end

            % Check if 'Timestamp' is given as name-value pair
            if isfield(pcap, 'Timestamp')
                timestamp = pcap.Timestamp;
                % Validate timestamp
                validateattributes(timestamp, {'numeric'}, ...
                    {'scalar', 'nonnegative'}, mfilename, 'Timestamp');
            else
                % Assign zero as the default value for timestamp
                timestamp = 0;
            end

            if isfield(pcap, 'Comment')
                comment = pcap.Comment;
                % Validate comment
                validateattributes(comment, {'char', 'string'}, {'row'}, ...
                    mfilename, 'Comment');
            else
                % Assign empty character as the default value
                comment = '';
            end

            % Form packet with radiotap header
            packet = [radiotapHeader; packetData];

            if obj.IsPCAPNG
                % Write packet into PCAPNG format file
                if isempty(comment)
                    obj.PCAPNGPacketWriter.write(timestamp, obj.InterfaceID, packet);
                else
                    obj.PCAPNGPacketWriter.write(timestamp, obj.InterfaceID, packet, comment);
                end
            else
                % Write packet into PCAP format file
                obj.PCAPPacketWriter.write(timestamp, packet);
            end
        end
    end
end
