classdef pcapDump < matlab.DiscreteEventSystem & matlab.system.mixin.SampleTime
%pcapDump Export PSDUs into PCAP/PCAPNG format file
%
%   This object performs the following functions on the physical layer
%   service data unit (PSDU)
%     - Deaggregates the PSDU if it is an aggregated MAC protocol
%     data unit (AMPDU)
%     - Writes MAC protocol data unit (MPDU) packets into packet capture
%     (PCAP)/packet capture next generation (PCAPNG) format file
%
%   This is an example helper class (Discrete Event System)

%   Copyright 2019 The MathWorks, Inc

properties(Nontunable)
    % Node ID
    NodeID = 0

    % Capture Format
    CaptureFormat = 'pcapng'

    % Capture
    Capture = 'Enable'
end

properties(Constant, Hidden)
    % Capture Format Types
    CaptureFormatSet = matlab.system.StringSet({'pcapng', 'pcap'});

    % PCAP Capture Values
    CaptureSet = matlab.system.StringSet({'Enable', 'Disable'});
end

% Pre-computed constants
properties(Access = private)
    % AMPDU flag to check if the PSDU is AMPDU
    IsAMPDU = 0

    % HelperWLANPacketWriter object
    PCAPWriter

    % HT configuration object
    HTConfig

    % VHT configuration object
    VHTConfig

    % HE configuration object
    HESUConfig

    % The time at which packet occurs
    TimeStamp = 0

    % Radio tap information. This includes modulation coding scheme,
    % channel bandwidth, and frame format of the packet
    Radiotap

    % Maximum number of users in a multi-user(MU) transmission using OFDMA.
    % In 20MHz OFDMA transmission max possible users are 9.
    MaxMUStations = 9
end

properties(Access = private)
    % Radiotap properties

    % Frame format
    FrameFormat = 0

    % Modulation coding scheme
    MCS

    % Channel bandwidth
    channelBandwidth = 20
end

% Discrete-event algorithms
methods
    function [entity, events] = psduEntry(obj, ~, entity, ~)
        %psduEntry deaggregates the PSDU if it is an AMPDU and MPDU
        %packets into PCAP/PCAPNG format file

        % Capture the PSDU if 'Capture' is Enabled.
        if(strcmp(obj.Capture, 'Enable'))

            for user = 1:numel(entity.data.psduLength)
                if entity.data.psduLength(user) ~= 0
                    % Get MPDU frame in bits
                    psduBits = entity.data.data(1:entity.data.psduLength(user)*8, user);

                    % Get the timestamp for each frame
                    obj.TimeStamp = obj.getCurrentTime()*1000000;

                    % Initializing for code generation
                    frameFormatstr = '';

                    switch obj.FrameFormat
                        case 0
                            frameFormatstr = 'Non-HT';
                        case 1
                            frameFormatstr = 'HT-Mixed';
                        case 2
                            frameFormatstr = 'VHT';
                        case 3
                            frameFormatstr = 'HE-SU';
                        case 4
                            frameFormatstr = 'HE-EXT-SU';
                        case 5
                            frameFormatstr = 'HE-MU-OFDMA';
                    end

                    % Create radiotap header
                    radiotap = createRadiotapHeader(frameFormatstr, obj.MCS(user), obj.channelBandwidth);

                    % Check if the frame is aggregated
                    if (obj.IsAMPDU)
                        ampdu = psduBits;
                        mpduList = {};

                        % Deaggregate AMPDU based on the frame format
                        switch obj.FrameFormat
                            case 1
                                obj.HTConfig.AggregatedMPDU = obj.IsAMPDU;
                                [mpduList, ~, ~] = wlanAMPDUDeaggregate(ampdu, obj.HTConfig,...
                                    'DataFormat', 'bits');
                            case 2
                                [mpduList, ~, ~] = wlanAMPDUDeaggregate(ampdu, obj.VHTConfig,...
                                    'DataFormat', 'bits');
                            case 3
                                obj.HESUConfig.ExtendedRange = 0;
                                [mpduList, ~, ~] = wlanAMPDUDeaggregate(ampdu, obj.HESUConfig,...
                                    'DataFormat', 'bits');
                            case 4
                                obj.HESUConfig.ExtendedRange = 1;
                                [mpduList,~,~] = wlanAMPDUDeaggregate(ampdu,obj.HESUConfig,...
                                    'DataFormat','bits');
                            case 5
                                obj.HESUConfig.ExtendedRange = 0;
                                [mpduList, ~, ~] = wlanAMPDUDeaggregate(ampdu, obj.HESUConfig,...
                                    'DataFormat', 'bits');
                        end

                        % Export the list of deaggregated MPDU
                        for idx = 1:numel(mpduList)
                            mpduListHex = mpduList{idx};
                            % Convert from hexadecimal to decimal
                            mpduListDec = hex2dec(mpduListHex);
                            % Write MPDUs based on the capture format
                            obj.PCAPWriter.write(mpduListDec, 'Radiotap', radiotap, 'Timestamp', obj.TimeStamp);
                        end
                    else
                        % Convert MPDU bits to decimal
                        mpduDec = bi2de(reshape(psduBits, 8, [])');

                        % Write MPDU based on capture format
                        obj.PCAPWriter.write(mpduDec, 'Radiotap', radiotap, 'Timestamp', obj.TimeStamp);
                    end
                end
            end
        end
        % Destroy the event after its action.
        events = obj.eventDestroy();
    end

    function [entity,events] = phyIndEntry(obj,~,entity,~)
    %phyIndEntry Configure the pcapDump properties using
    %vector information

        % Capture the information in Tx start request and Rx start
        % indication
        if (entity.data.msgType == PhyPrimitives.RxStartIndication) || (entity.data.msgType == PhyPrimitives.TxStartRequest)
            obj.IsAMPDU = (entity.data.aggregatedMPDU);
            obj.FrameFormat = entity.data.frameFormat;
            obj.MCS = entity.data.mcsIndex;
            obj.channelBandwidth = entity.data.channelBandwidth;
        end

        % Destroy the event after its action.
        events = obj.eventDestroy();
    end
end

methods(Access = protected)
    function entityTypes = getEntityTypesImpl(obj)
        %getEntityTypesImpl Specify the entity types that are used

        entityTypes = [obj.entityType('psdu','Frame'), obj.entityType('phyInd','PhyInd')];
    end

    function [inputTypes,outputTypes] = getEntityPortsImpl(~)
        %getEntityPortsImpl Specify input and output port data types
        %and complexity

        inputTypes = {'psdu', 'phyInd'};
        outputTypes = {};
    end

    function [storageSpec,I,O] = getEntityStorageImpl(obj)
        %getEntityStorageImpl Create internal storage queues and associate
        %those input and output ports

        storageSpec = [obj.queueFIFO('psdu', 1), obj.queueFIFO('phyInd', 1)];
        I = [1,2];
        O = [];
    end

    function setupImpl(obj)
        %setupImpl Set default values

        % HT configuration object
        obj.HTConfig = wlanHTConfig();
        % VHT configuration object
        obj.VHTConfig = wlanVHTConfig();
        % HE configuration object
        obj.HESUConfig = wlanHESUConfig();

        % Preinitialize MCS
        obj.MCS = zeros(obj.MaxMUStations, 1);

        % Get simulink model name
        modelName = bdroot;
        % Get the cell array, where each element consists of a complete
        % path to a node configured by the user.
        nodePaths = dcfGetNodeNamesList(modelName);
        nodeNames = cell(1, numel(nodePaths));
        % Get the node name
        for i = 1:numel(nodePaths)
            nodeNames{i} = strrep(nodePaths{i}, [modelName '/'], '');
        end

        % Capture the PSDU if 'Capture' is enabled.
        if(strcmp(obj.Capture, 'Enable'))
            % Instantiate the HelperPCAPWriter object
            obj.PCAPWriter = HelperWLANPacketWriter('FileName', [nodeNames{obj.NodeID}], ...
                'FileExtension', obj.CaptureFormat, 'RadiotapFlag', true);
        end
    end

    function num = getNumInputsImpl(~)
        % PcapDump block contains 2 input ports, one for the input PSDU
        % from MAC, and the other one for 'PhyInd'

        num = 2;
    end

    function num = getNumOutputsImpl(~)
        % PcapDump block contains no output ports

        num = 0;
    end
end

methods(Access = protected, Static)
    function flag = showSimulateUsingImpl
        % Return false if simulation mode is hidden in system block dialog

        flag = true;
    end
end
end