classdef blePCAPWriter < handle & comm.internal.ConfigBase & comm_sysmod.internal.pcapCommon
    %blePCAPWriter PCAP or PCAPNG file writer of Bluetooth LE LL packets
    %
    %   OBJ = blePCAPWriter creates a default Bluetooth(R) low energy (LE)
    %   packet capture (PCAP) or packet capture next generation (PCAPNG)
    %   file writer object that enables you to write Bluetooth LE link
    %   layer (LL) packets into a file with the .pcap or .pcapng extension.
    %
    %   OBJ = blePCAPWriter(Name, Value) creates a Bluetooth LE PCAP or
    %   PCAPNG file writer object, OBJ, with properties specified by one or
    %   more name-value pairs. You can specify additional name-value pair
    %   arguments in any order as (Name1,Value1,...,NameN,ValueN). When you
    %   do not specify a property name and value, the object uses the
    %   default value.
    %
    %   OBJ = blePCAPWriter('PCAPWriter', PCAPOBJ) creates a Bluetooth LE
    %   PCAP or PCAPNG file writer object, OBJ, using the configuration
    %   specified in PCAPOBJ. PCAPOBJ is an object of type <a
    %   href="matlab:help('pcapngWriter')">pcapngWriter</a> or <a
    %   href="matlab:help('pcapWriter')">pcapWriter</a>.
    %
    %   OBJ = blePCAPWriter('PCAPWriter', PCAPOBJ, 'PhyHeaderPresent',
    %   ISPRESENT) creates a Bluetooth LE PCAP or PCAPNG file writer
    %   object, OBJ, using the configuration specified in PCAPOBJ. To write
    %   the physical layer (PHY) header into the pcap file with the packet,
    %   set the ISPRESENT input as true.
    %
    %   blePCAPWriter methods:
    %   write              - Write Bluetooth LE link layer packet contents into
    %                        a file with the .pcap or .pcapng extension
    %
    %   blePCAPWriter Name-Value pairs:
    %   FileName           - File name specified as a character row
    %                        vector or string. The default file name is
    %                        'bleCapture'.
    %   ByteOrder          - Byte order, specified as 'little-endian' or
    %                        'big-endian'. The default value is
    %                        'little-endian'.
    %   FileExtension      - File extension as 'pcap' or 'pcapng'. The default
    %                        value is 'pcap'.
    %   FileComment        - Additional info given by the user as a comment for
    %                        the file, specified as a character row vector or a
    %                        string. The default value is an empty character
    %                        array. To enable this property, set the
    %                        'FileExtension' property to 'pcapng'.
    %   Interface          - Name of the device that captures packets, specified
    %                        as a character row vector or a string. The
    %                        default value is 'BLE'. To enable this
    %                        property, set the 'FileExtension' property to
    %                        'pcapng'.
    %   PhyHeaderPresent   - Flag to indicate whether PHY header is present.
    %                        The default value is false.
    %   PCAPWriter         - Object of type <a
    %                        href="matlab:help('pcapngWriter')">pcapngWriter</a>
    %                        or <a
    %                        href="matlab:help('pcapWriter')">pcapWriter</a>.
    %                        When you set this property, OBJ derives the
    %                        FileName, FileExtension, FileComment, and
    %                        ByteOrder properties in accordance from the
    %                        PCAPOBJ input.
    %
    %   % Example 1:
    %   %  Write Bluetooth LE link layer packet to PCAP format file
    %
    %   % Create a Bluetooth LE object with file name 'bleExample1.pcap'
    %   pcapObj = blePCAPWriter('FileName', 'bleExample1', ...
    %       'FileExtension', 'pcap');
    %
    %   % Create a Bluetooth LE link layer packet
    %   cfgLLData = bleLLDataChannelPDUConfig('LLID', ...
    %       'Data (start fragment/complete)');
    %   payload = '0E00050014010A001F004000170017000000';
    %   llDataPDU = bleLLDataChannelPDU(cfgLLData, payload);
    %   msbfirst = 0;
    %   connAccessAddress = int2bit(hex2dec('E213BC42'), 32, msbfirst);
    %   llpacket = [connAccessAddress; llDataPDU];
    %
    %   % Write Bluetooth LE link layer packet to PCAP format file
    %   timestamp = 0; % Number of microseconds
    %   write(pcapObj, llpacket, timestamp, 'PacketFormat', 'bits');
    %
    %   % Example 2:
    %   %  Write Bluetooth LE link layer packet to PCAPNG format file
    %
    %   % Create a Bluetooth LE object with file name 'bleExample2.pcapng'
    %   pcapObj = blePCAPWriter('FileName', 'bleExample2', ...
    %       'FileExtension', 'pcapng');
    %
    %   % Create a Bluetooth LE link layer packet
    %   cfgLLData = bleLLDataChannelPDUConfig('LLID', ...
    %       'Data (start fragment/complete)');
    %   payload = '0E00050014010A001F004000170017000000';
    %   llDataPDU = bleLLDataChannelPDU(cfgLLData, payload);
    %   msbfirst = 0;
    %   connAccessAddress = int2bit(hex2dec('E213BC42'), 32, msbfirst);
    %   llpacket = [connAccessAddress; llDataPDU];
    %
    %   % Write Bluetooth LE link layer packet to PCAPNG format file
    %   timestamp = 12800000; % Number of microseconds
    %   write(pcapObj, llpacket, timestamp, 'PacketFormat', 'bits');
    %
    %   % Example 3:
    %   %  Write Bluetooth LE link layer packet to PCAPNG format file using a
    %   %  PCAPWriter object
    %
    %   % Create a PCAPNG object with file name 'bleExample3.pcapng'
    %   pcapObj = pcapngWriter('FileName', 'bleExample3',  ...
    %       'FileComment', 'This is a sample file');
    %
    %   % Create a Bluetooth LE PCAP object with file name 'bleExample3.pcapng'.
    %   % Set PhyHeaderPresent flag to true to indicate that PHY header will be
    %   % included in the PCAPNG file.
    %   blePCAP = blePCAPWriter('PCAPWriter', pcapObj, 'PhyHeaderPresent', true);
    %
    %   % Generate an Advertising indication PDU
    %   cfgLLAdv = bleLLAdvertisingChannelPDUConfig;
    %   cfgLLAdv.PDUType         = 'Advertising indication';
    %   cfgLLAdv.AdvertisingData = '020106';
    %   llDataPDU = bleLLAdvertisingChannelPDU(cfgLLAdv);
    %   msbfirst = 0;
    %   connAccessAddress = int2bit(hex2dec('E213BC42'), 32, msbfirst);
    %   llpacket = [connAccessAddress; llDataPDU];
    %
    %   % Write Bluetooth LE link layer packet to PCAPNG format file
    %   PhyHeaderBytes = [39 10 8 1 10 10 10 10 15 00];
    %   timestamp = 18912345; % Number of microseconds
    %   write(blePCAP, llpacket, timestamp, 'PacketFormat', 'bits', ...
    %       'PhyHeader', PhyHeaderBytes, 'PacketComment', 'This is the first packet');
    %
    %   See also pcapWriter, pcapngWriter

    %   Copyright 2020-2022 The MathWorks, Inc.

    %#codegen

    properties (Dependent, SetAccess = private)
        %FileName PCAP or PCAPNG file name
        %   Specify file name as a character row vector or string. The
        %   default file name is 'bleCapture'.
        FileName

        %ByteOrder Byte order
        %   Specify the byte order as 'little-endian' or 'big-endian'. The
        %   default value is 'little-endian'.
        ByteOrder

        %FileComment Comment for the file
        %   Specify any additional comment for the file as a character row
        %   vector or a string. To enable this property, set the
        %   'FileExtension' property to 'pcapng'. The default value is an
        %   empty character array.
        FileComment
    end

    properties (GetAccess = public, SetAccess = private)
        %FileExtension Extension of the file
        %   Specify the extension as 'pcap' or 'pcapng'. The default value
        %   is 'pcap'.
        FileExtension = 'pcap'

        %Interface Name of the device used to capture data
        %   Specify interface as a character vector or a string in UTF-8
        %   format. To enable this property, set the 'FileExtension'
        %   property to 'pcapng'. The default value is 'BLE'.
        Interface = 'BLE'

        %PhyHeaderPresent Flag to indicate whether PHY header is present
        %   Set this property to true to indicate that the PHY header is
        %   present. The default value is false.
        PhyHeaderPresent (1, 1) logical = false

        %PCAPWriter Packet writer object
        %   Set this property as of type <a
        %   href="matlab:help('pcapngWriter')">pcapngWriter</a> or <a
        %   href="matlab:help('pcapWriter')">pcapWriter</a>. When
        %   PCAPWriter is set, the properties FileName, FileExtension,
        %   FileComment, and ByteOrder are taken from the object specified
        %   in the PCAPWriter.
        PCAPWriter
    end

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

        %IsPCAPNG Indicates the file format of the file (pcap or pcapng)
        IsPCAPNG (1, 1) logical = false;

        %PCAPPacketWriter PCAP packet writer object
        PCAPPacketWriter

        %PCAPNGPacketWriter PCAPNG packet writer object
        PCAPNGPacketWriter
    end

    properties (Hidden)
        %DisableValidation Disable the validation for input arguments of
        %write method
        %  Specify this property as a scalar logical. When true, validation
        %  is not performed on the input arguments and the packet is
        %  expected to be in decimal format.
        DisableValidation (1, 1) logical = false
    end

    properties (Constant, Hidden)
        %LinkType Link type for Bluetooth LE protocol without physical
        %layer
        LinkType = 251

        %PhyHeaderLinkType Link type for Bluetooth LE packet with physical
        %layer header
        PhyHeaderLinkType = 256

        FileExtension_Values = {'pcap', 'pcapng'};
    end

    methods (Access = protected)
        function flag = isInactiveProperty(obj, prop)
            flag = false;
            if strcmp(prop,'FileComment') || strcmp(prop,'Interface')
                flag = strcmp(obj.FileExtension, 'pcap');
            end
        end
    end

    methods (Access = private)
        function setFileExtension(obj, value)
            propName = 'FileExtension';
            value = validateEnumProperties(obj, propName, value);
            obj.(propName) = '';
            obj.(propName) = value;
        end

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

        function setPCAPWriter(obj, value)
            validateattributes(value, {'pcapngWriter', 'pcapWriter'}, ...
                {'row'}, mfilename, 'PCAPWriter object');
            obj.PCAPWriter = value;
        end
    end

    methods
        function obj = blePCAPWriter(varargin)
            % Name-value pair check
            coder.internal.errorIf(mod(nargin,2) == true, ...
                'MATLAB:system:invalidPVPairs');

            % Initialize packet writer flag
            packetWriterFlag = false;

            fileName = 'bleCapture';
            byteOrder = 'little-endian';
            fileComment = blanks(0);

            if isempty(coder.target) % Simulation path
                dummyFileName = blanks(0);
                % Apply name-value pairs
                for i = 1:2:nargin

                    value = validatestring(varargin{i}, {'FileName', ...
                        'ByteOrder', 'FileComment', 'FileExtension', ...
                        'Interface', 'PhyHeaderPresent', 'PCAPWriter'}, ...
                        mfilename);

                    switch(value)
                        case 'FileName'
                            fileName = varargin{i+1};
                        case 'FileComment'
                            fileComment = varargin{i+1};
                        case 'ByteOrder'
                            byteOrder = varargin{i+1};
                        case 'FileExtension'
                            setFileExtension(obj, varargin{i+1});
                        case 'Interface'
                            setInterface(obj, varargin{i+1});
                        case 'PhyHeaderPresent'
                            obj.PhyHeaderPresent = varargin{i+1};
                        otherwise % PCAPWriter
                            packetWriterFlag = true;
                            setPCAPWriter(obj, varargin{i+1});
                    end

                    coder.internal.errorIf(packetWriterFlag && ...
                        (strcmp(value, 'FileName') || ...
                        strcmp(value, 'FileExtension') || ...
                        strcmp(value, 'ByteOrder') || ...
                        strcmp(value, 'FileComment')), ...
                        'bluetooth:blePCAPWriter:PCAPBLEInvalidParams');
                end
            else % Codegen path
                % File name for dummy pcapWriter or pcapngWriter for
                % codegen, which will be used to create the object. No file
                % will be created using this dummy file name.
                dummyFileName = 'dummyFileForCodeGen';
                nvPairs = struct('FileName', uint32(0), ...
                    'FileComment', uint32(0), ...
                    'ByteOrder', uint32(0), ...
                    'FileExtension', uint32(0), ...
                    'Interface', uint32(0), ...
                    'PhyHeaderPresent', uint32(0), ...
                    'PCAPWriter', uint32(0) ...
                    );

                % Parse inputs
                popts = struct('PartialMatching', true, 'CaseSensitivity', ...
                    false);
                pStruct = coder.internal.parseParameterInputs(nvPairs, ...
                    popts, varargin{:});

                if pStruct.PCAPWriter
                    packetWriterFlag = true;
                end
                coder.internal.errorIf(packetWriterFlag && ...
                    (pStruct.FileName || ...
                    pStruct.FileExtension || ...
                    pStruct.ByteOrder || ...
                    pStruct.FileComment), ...
                    'bluetooth:blePCAPWriter:PCAPBLEInvalidParams');

                % Get values for the N-V pair or set defaults for the
                % optional arguments
                byteOrder = coder.internal.getParameterValue( ...
                    pStruct.ByteOrder, coder.const('little-endian'), ...
                    varargin{:});

                fileName = coder.internal.getParameterValue( ...
                    pStruct.FileName, 'bleCapture', varargin{:});

                fileComment = coder.internal.getParameterValue( ...
                    pStruct.FileComment, blanks(0), varargin{:});

                obj.PhyHeaderPresent = coder.internal.getParameterValue( ...
                    pStruct.PhyHeaderPresent, false, ...
                    varargin{:});

                setFileExtension(obj, coder.internal.getParameterValue( ...
                    pStruct.FileExtension, coder.const('pcap'), ...
                    varargin{:}));

                setInterface(obj, coder.internal.getParameterValue( ...
                    pStruct.Interface, coder.const('pcap'), ...
                    varargin{:}));

                defaultVal = pcapWriter('FileName', dummyFileName);
                setPCAPWriter(obj, coder.internal.getParameterValue( ...
                    pStruct.PCAPWriter, defaultVal, ...
                    varargin{:}));
            end

            % Set link type based on PHY header presence
            if obj.PhyHeaderPresent
                linkType = obj.PhyHeaderLinkType;
            else
                linkType = obj.LinkType;
            end

            % File name for pcap and pcapng files.
            if (~packetWriterFlag && strcmp(obj.FileExtension, 'pcap')) || ...
                    (packetWriterFlag && isa(obj.PCAPWriter, 'pcapWriter'))
                pcapFileName = fileName;
                pcapngFileName = dummyFileName;
            else
                pcapFileName = dummyFileName;
                pcapngFileName = fileName;
                obj.IsPCAPNG = true;
            end

            % Check if 'PCAPWriter' object is passed as a name-value pair
            if packetWriterFlag
                if isa(obj.PCAPWriter, 'pcapWriter')
                    obj.FileExtension = 'pcap';
                    obj.PCAPPacketWriter = obj.PCAPWriter;

                    % Initialize for codegen support
                    if ~isempty(coder.target)
                        obj.PCAPNGPacketWriter = pcapngWriter('FileName', pcapngFileName,...
                            'ByteOrder', byteOrder, 'FileComment', fileComment);
                    end

                    coder.internal.errorIf(obj.PCAPPacketWriter.GlobalHeaderPresent,...
                        'bluetooth:blePCAPWriter:MultipleInterfacesNotAccepted');
                else
                    obj.FileExtension = 'pcapng';
                    obj.PCAPNGPacketWriter = obj.PCAPWriter;

                    % Initialize for codegen support
                    if ~isempty(coder.target)
                        obj.PCAPPacketWriter = pcapWriter('FileName', pcapFileName,...
                            'ByteOrder', byteOrder);
                    end
                end
            else
                % Initialize for codegen support
                if ~isempty(coder.target)
                    obj.PCAPPacketWriter = pcapWriter('FileName', pcapFileName,...
                        'ByteOrder', byteOrder);
                    obj.PCAPNGPacketWriter = pcapngWriter('FileName', pcapngFileName,...
                        'ByteOrder', byteOrder, 'FileComment', fileComment);
                else
                    if strcmp(obj.FileExtension,'pcap')
                        obj.PCAPPacketWriter = pcapWriter('FileName', pcapFileName,...
                            'ByteOrder', byteOrder);
                    else
                        obj.PCAPNGPacketWriter = pcapngWriter('FileName', pcapngFileName,...
                            'ByteOrder', byteOrder, 'FileComment', fileComment);
                    end
                end

                if strcmp(obj.FileExtension, 'pcap')
                    if ~isempty(fileComment)
                        coder.internal.warning('bluetooth:blePCAPWriter:PCAPBLEIgnoreFileComment');
                    end
                end
            end

            if obj.IsPCAPNG
                % Write the Interface description block
                obj.InterfaceID = obj.PCAPNGPacketWriter.writeInterfaceDescriptionBlock(linkType, ...
                    obj.Interface);
            else
                % Write the Global header block
                obj.PCAPPacketWriter.writeGlobalHeader(linkType);
            end
        end

        function value = get.FileName(obj)
            if obj.IsPCAPNG
                value = obj.PCAPNGPacketWriter.FileName;
            else
                value = obj.PCAPPacketWriter.FileName;
            end
        end

        function value = get.FileComment(obj)
            if obj.IsPCAPNG
                value = obj.PCAPNGPacketWriter.FileComment;
            else
                value = blanks(0);
            end
        end

        function value = get.ByteOrder(obj)
            if obj.IsPCAPNG
                value = obj.PCAPNGPacketWriter.ByteOrder;
            else
                value = obj.PCAPPacketWriter.ByteOrder;
            end
        end

        function write(obj, packet, timestamp, varargin)
            %write Write a packet into a file with the .pcap or .pcapng
            %extension
            %
            %   write(OBJ, PACKET, TIMESTAMP) writes a packet into a file
            %   with the .pcap or .pcapng extension
            %
            %   PACKET is the Bluetooth LE link layer packet specified one
            %   of these types:
            %    - A binary vector representing bits 
            %    - 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
            %      [0, 255], representing octets in decimal format
            %    - An n-by-2 character array, where each row represents
            %      an octet in hexadecimal format
            %
            %   TIMESTAMP is specified as a scalar integer greater than or
            %   equal to 0. Timestamp is the packet arrival time in
            %   microseconds since 1/1/1970.
            %
            %   write(OBJ, PACKET, TIMESTAMP, Name, Value) specifies
            %   additional name-value pair arguments described below. When
            %   a name-value pair is not specified, its default value is
            %   used.
            %
            %  'PhyHeader'       - PhyHeader holds the packet metadata
            %                      provided by the user in addition to the
            %                      packet, specified as one of these types:
            %                       - A binary vector representing bits
            %                       - 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 [0, 255], representing
            %                         octets in decimal format
            %                       - An n-by-2 character array, where each
            %                         row represents an octet in hexadecimal
            %                         format
            %  'PacketComment'   - Packet comment, specified as a character
            %                      vector or string. The default value is an
            %                      empty character vector.
            %  'PacketFormat'    - Specifies the format of the input data
            %                      packet as 'bits' or 'octets'. The
            %                      default value is 'octets'. If it is
            %                      specified as 'octets', the packet can be
            %                      a numeric vector representing octets in
            %                      decimal format or alternatively, it can
            %                      be a character array or string scalar
            %                      representing octets in hexadecimal
            %                      format. Otherwise, packet is a binary
            %                      vector.
            %  'PhyHeaderFormat' - Specifies the PHY header of the input data
            %                      as 'bits' or 'octets'. The default value
            %                      is 'octets'. If it is specified as
            %                      'octets', the PHY header can be a
            %                      numeric vector representing octets in
            %                      decimal format or alternatively, it can
            %                      be a character array or string scalar
            %                      representing octets in hexadecimal
            %                      format. Otherwise, PHY header is a
            %                      binary vector.

            narginchk(3, 11);

            % Check whether the validation is enabled
            if ~obj.DisableValidation
                % Name-value pair check
                coder.internal.errorIf(mod(nargin-3,2) == true,...
                    'MATLAB:system:invalidPVPairs');
            end

            if isempty(coder.target) % Simulation path
                % Initialize
                packetFormat = 'octets';
                packetComment = blanks(0);
                phyheaderFormat = 'octets';
                phyheaderInput = false;

                % Apply name-value pairs
                for i = 1:2:nargin-3
                    value = validatestring(varargin{i}, {'PacketFormat',...
                        'PacketComment', 'PhyHeader', 'PhyHeaderFormat'}, ...
                        mfilename);

                    switch(value)
                        case 'PacketFormat'
                            packetFormat = varargin{i+1};
                        case 'PhyHeader'
                            phyheader = varargin{i+1};
                            phyheaderInput = true;
                        case 'PhyHeaderFormat'
                            phyheaderFormat = varargin{i+1};
                        otherwise % PacketComment
                            packetComment = varargin{i+1};
                    end
                end
            else % Codegen path
                nvPairs = struct('PacketFormat', uint32(0), ...
                    'PacketComment', uint32(0), ...
                    'PhyHeader', uint32(0), ...
                    'PhyHeaderFormat', uint32(0));

                % Select parsing options
                popts = struct('PartialMatching', true,...
                    'CaseSensitivity',false);

                % Parse inputs
                pStruct = coder.internal.parseParameterInputs( ...
                    nvPairs,popts,varargin{:});

                % Get values for the N-V pair or set defaults for the
                % optional arguments
                packetFormat = coder.internal.getParameterValue( ...
                    pStruct.PacketFormat, coder.const('octets'), ...
                    varargin{:});

                packetComment = coder.internal.getParameterValue( ...
                    pStruct.PacketComment, blanks(0), varargin{:});

                phyheaderFormat = coder.internal.getParameterValue( ...
                    pStruct.PhyHeaderFormat, coder.const('octets'), ...
                    varargin{:});

                phyheader = coder.internal.getParameterValue( ...
                    pStruct.PhyHeader, blanks(0), varargin{:});
                if isempty (phyheader)
                    phyheaderInput = false;
                else
                    phyheaderInput = true;
                end
            end

            % Validate packet format
            packetFormat = validatestring(packetFormat, {'bits', 'octets'}, ...
                mfilename, 'PacketFormat');

            % Validate PHY header format
            phyheaderFormat = validatestring(phyheaderFormat, {'bits', 'octets'}, ...
                mfilename, 'PhyHeaderFormat');

            % Check if PhyHeader is passed as name-value pair
            if phyheaderInput
                % Validate PhyHeader and return octets in decimal format
                phyheaderData = obj.validatePayloadFormat(phyheader, phyheaderFormat);

                % Length of the PHY header
                phyHeaderLength = 10;

                if obj.PhyHeaderPresent
                    coder.internal.errorIf(numel(phyheaderData) ~= phyHeaderLength,...
                        'bluetooth:blePCAPWriter:PCAPBLEInvalidPHYheader');
                else
                    coder.internal.warning('bluetooth:blePCAPWriter:PCAPBLEIgnorePhyHeader');
                    phyheaderData = [];
                end
            else
                phyheaderData = [];
                coder.internal.errorIf(obj.PhyHeaderPresent, ...
                    'bluetooth:blePCAPWriter:PCAPBLENoPHYHeader');
            end

            % Check whether the validation is enabled
            if ~obj.DisableValidation
                % Validate packet and return octets in decimal format
                packetData = obj.validatePayloadFormat(packet, packetFormat);
            else
                obj.PCAPNGPacketWriter.DisableValidation = true;
                obj.PCAPPacketWriter.DisableValidation = true;
                packetData = double(packet);
            end

            % Form packet
            packet = [phyheaderData; packetData];

            if obj.IsPCAPNG
                % Write packet to PCAPNG format file
                if isempty(packetComment)
                    obj.PCAPNGPacketWriter.write(packet, timestamp, obj.InterfaceID);
                else
                    obj.PCAPNGPacketWriter.write(packet, timestamp, obj.InterfaceID, ...
                        'PacketComment', packetComment);
                end
            else
                % Write packet to PCAP format file
                obj.PCAPPacketWriter.write(packet, timestamp);
            end
        end
    end
end