function  pcapBytes = zigbeeExportToPcap(MPDUs, filename)
%zigbeeExportToPcap Export MAC frames into PCAP (packet capture) file
%   zigbeeExportToPcap(MPDUS, FILENAME) create file FILENAME containing the
%   MAC protocol data units MPDUs in a PCAP (packet capture) format.
%
%   PCAPBYTES = zigbeeExportToPcap(MPDUS, FILENAME) behaves as the previous
%   syntax, but also outputs the PCAP-formatted bytes.
%
%   Such PCAP files can be visualized using packet analyzers such as
%   wireshark. Currently, this function only supports ZigBee frames. 
%
%   Reference : https://wiki.wireshark.org/Development/LibpcapFileFormat

% Copyright 2017-2021 The MathWorks, Inc.

  % total number of octets for all input MPDUs
  if iscell(MPDUs)
    numOctets = sum(cellfun(@numel, MPDUs))/8;
  else
    numOctets = numel(MPDUs)/8;
  end
  
  % total number of octets to generate pcap file (Excluding global header octets)
  packetHeaderLen = 16;
  globalHeaderLen = 24;
  numOctets = numOctets + packetHeaderLen *length(MPDUs) + globalHeaderLen;

  % Pre-allocating the pcap octets
  pcapBytes = zeros(numOctets, 1);
  curPos = 0;
  
  % Global Header of the pcap file
  globalHdr = struct('magic_number', 'a1b2c3d4', ... % used to detect the file format itself and the byte ordering
                     'version_major', '0002', ...    % Major Version Number (Here, version 2.4 is used).
                     'version_minor', '0004', ...    % Minor Version Number (Here, version 2.4 is used). 
                     'thiszone', '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', '000000c3');         % data link type (195 is for zigbee protocol)

  % Converting global header into octets and store it into pcapBytes
  pcapBytes(1:globalHeaderLen) = [hex2dec(reshape(globalHdr.magic_number, 2, [])'); ...
                     hex2dec(reshape(globalHdr.version_major, 2, [])'); ...
                     hex2dec(reshape(globalHdr.version_minor, 2, [])'); ...
                     hex2dec(reshape(globalHdr.thiszone, 2, [])'); ...
                     hex2dec(reshape(globalHdr.sigfigs, 2, [])'); ...
                     hex2dec(reshape(globalHdr.snaplen, 2, [])'); ...
                     hex2dec(reshape(globalHdr.network, 2, [])')];
  curPos = curPos + globalHeaderLen;

  if iscell(MPDUs)
    % for multiple MPDUs
    for idx = 1:length(MPDUs)
      % Generating pcap bytes for single MPDU
      thisLen = packetHeaderLen + (numel(MPDUs{idx})/8);
      pcapBytes(curPos+1 : curPos+thisLen) = generateForSingleMPDU(MPDUs{idx}, idx-1);
      curPos = curPos + thisLen;
    end
  else
    % for single MPDU
    pcapBytes = generateForSingleMPDU(MPDUs, 0);
  end

  % Create pcap file
  fid = fopen(filename, 'w');
  if fid == -1 
    % We must be in a write-protected location, e.g.,
    % \toolbox\comm\commdemos in an install.
    % Try userpath:
    path = fullfile(userpath, filename);
    fid = fopen(path, 'w');
    if fid ~= -1
      warning(message('zigbee:ZigBee:PcapAtUserpath', path));
      % later on we will write to the file
    else
      error(message('zigbee:ZigBee:UserpathUnwritable'));
    end
  end
  % Writing pcap bytes
  fwrite(fid, pcapBytes);
  fclose(fid);
end


function pcapBytes = generateForSingleMPDU(MPDU, idx)
%generateForSingleMPDU converting single MPDU into pcap (packet capture format) bytes
%   pcapBytes = generateForSingleMPDU(MPDU, idx) converting single
%   MPDU into pcap (packet capture format) bytes, adds the timestamp
%   to given MPDU and returns it.

decNums = bit2int(MPDU(:), 8);

% calculating inclusion length and actual length
if numel(decNums) > 65535
  incl_len = 65535;
  % truncating the extra bytes (more than 65535 bytes)
  decNums = decNums(1:65535);
else
  incl_len = numel(decNums);
end
incl_len = dec2hex(incl_len, 8);
len = dec2hex(numel(decNums), 8);

% calculating UTC seconds
sec = fix(posixtime(datetime));

% Forming Packet Header
pktHdr = struct('ts_sec', dec2hex(sec, 8), ...  % timestamp seconds
                'ts_usec', dec2hex(rem(idx, 1000000), 8), ... % timestamp microseconds
                'incl_len', incl_len, ... % number of octets of packet saved in file
                'orig_len', len);    % actual length of packet

% Packet Header | Packet Bytes (concatenating)
pcapBytes = [hex2dec(reshape(pktHdr.ts_sec, 2, [])'); ...
             hex2dec(reshape(pktHdr.ts_usec, 2, [])'); ...
             hex2dec(reshape(pktHdr.incl_len, 2, [])'); ...
             hex2dec(reshape(pktHdr.orig_len, 2, [])'); decNums];
end
