function pcapBytes = helperWLANExportToPCAP(MPDUs, fileName)
%helperWLANExportToPCAP exports WLAN MAC frames into a packet capture (PCAP)
%file
%   helperWLANExportToPCAP(MPDUS, FILENAME) exports MAC frames into PCAP
%   format with the given file name.
%
%   MPDUS    - Cell array containing WLAN MAC frames. Each element is a 
%              character array, where each row is the hexadecimal 
%              representation of an octet.
%   FILENAME - Name of the PCAP file.
%
%   This PCAP file can be visualized using packet analyzers such as
%   Wireshark.
%
%   Reference : https://wiki.wireshark.org/Development/LibpcapFileFormat
%               https://www.tcpdump.org/linktypes.html

% Copyright 2018 The MathWorks, Inc.

% Calculate total number of octets for the given MPDUs
octets = 0;
for i=1:length(MPDUs)
  % Add radiotap header to the frame
  MPDUs{i} = addRadioTapHeader(MPDUs{i});
  octets = octets + (numel(MPDUs{i})/2);
end

% Protocol number for the WLAN frames with radiotap header.
protocolNumber = 127;

% Packet header size (16 octets)
packetHeaderSize = 16;

% Global header size (24 octets)
globalHeaderSize = 24;

% Total number of octets to generate PCAP file
octets = octets + packetHeaderSize*length(MPDUs) + globalHeaderSize;

% Pre-allocating the PCAP octets
pcapBytes = zeros(octets, 1);
curPos = 0;

% Global Header of the PCAP file
globalHdr = struct('magicNumber', 'a1b2c3d4', ...           % Used to detect the file format and the byte ordering
                   'versionMajor', '0002', ...              % Major Version Number (Here, version 2.4 is used).
                   'versionMinor', '0004', ...              % Minor Version Number (Here, version 2.4 is used). 
                   'timezone', '00000000', ...              % GMT to local correction (Not Used)
                   'sigfigs', '00000000', ...               % Accuracy of timestamps (Not Used)
                   'snapLen', '0000ffff', ...               % Max length of captured packets, in octets (65535)
                   'network', dec2hex(protocolNumber, 8));  % Data link type (protocol number)

% Converting global header into octets and store it into pcapBytes
pcapBytes(1:24) = [hex2dec(reshape(globalHdr.magicNumber, 2, [])'); ...
                   hex2dec(reshape(globalHdr.versionMajor, 2, [])'); ...
                   hex2dec(reshape(globalHdr.versionMinor, 2, [])'); ...
                   hex2dec(reshape(globalHdr.timezone, 2, [])'); ...
                   hex2dec(reshape(globalHdr.sigfigs, 2, [])'); ...
                   hex2dec(reshape(globalHdr.snapLen, 2, [])'); ...
                   hex2dec(reshape(globalHdr.network, 2, [])')];
curPos = curPos + 24;

for idx = 1:length(MPDUs)
  % Generate PCAP bytes for a single MPDU
  pcapBytes(curPos+1 : curPos+16+(numel(MPDUs{idx})/2)) = generateForSingleMPDU(MPDUs{idx}, idx-1);
  curPos = curPos + 16 + (numel(MPDUs{idx})/2);
end

% Create PCAP file
[fid, errMsg] = fopen(fileName, 'w');
if fid == -1
  coder.internal.errorIf((fid == -1) && ~isempty(errMsg), 'wlan:helperWLANExportToPCAP:FileError', errMsg);
end

% Write PCAP bytes into file
fwrite(fid, pcapBytes);
% Close file
fclose(fid);
end

% Convert a single MPDU into PCAP bytes
function pcapBytes = generateForSingleMPDU(MPDU, idx)
  decNums = hex2dec(MPDU);

  packetLen = numel(decNums);
  
  % Maximum length of a packet in the PCAP file (65535 octets)
  maxPacketLen = 65535;
  
  % Calculate inclusion length
  inclLen = min(packetLen, maxPacketLen);
  decNums = decNums(1:inclLen);

  inclLen = dec2hex(inclLen, 8);
  len = dec2hex(numel(decNums), 8);

  % Calculate UTC seconds
  utcSec = fix(posixtime(datetime));

  % Form packet header
  pktHeader = struct('tsSec', dec2hex(utcSec, 8), ...          % Timestamp seconds
                  'tsUsec', dec2hex(rem(idx, 1000000), 8), ... % Timestamp microseconds
                  'inclLen', inclLen, ...                      % Number of octets of packet saved in file
                  'origLen', len);                             % Actual length of packet

  % Packet Header | Packet Bytes (concatenate)
  pcapBytes = [hex2dec(reshape(pktHeader.tsSec, 2, [])'); ...
               hex2dec(reshape(pktHeader.tsUsec, 2, [])'); ...
               hex2dec(reshape(pktHeader.inclLen, 2, [])'); ...
               hex2dec(reshape(pktHeader.origLen, 2, [])'); decNums];
end

% Add radiotap information to the frame
function frame = addRadioTapHeader(frame)
  % Header length (11 octets)
  headerLen = ['09'; '00']; 

  % Radiotap version
  radiotapVersion = '00';
  padding = '00';

  % Only 'radiotap flags' field is present
  presentFlags = ['02'; '00'; '00'; '00'];

  % FCS is included at the end of MAC frame
  radiotapFlags = '10';

  frame = [radiotapVersion; padding; headerLen; presentFlags; radiotapFlags; frame];
end
