function [cfg, varargout] = APSFrameDecoder(appFrame)
%APSFrameDecoder Decode ZigBee application-layer frames
%   CFG = APSFrameDecoder(APPFRAME) decodes the APP frame APPFRAME
%   and outputs all decoded information to the zigbee.APPFrameConfig object CFG.
%
%   [CFG, PAYLOAD] = APSFrameDecoder(APPFRAME) is the same as the
%   syntax above, except that it additionally outputs the APP frame
%   payload. This syntax is allowed only for data frame types.
%
%   See also zigbee.APSFrameConfig, zigbee.APSFrameGenerator

%   Copyright 2016-2023 The MathWorks, Inc.

% convert bytes to bits
appFrameBin = zigbee.internal.bytes2bits(appFrame);

% 0. Initialize:
cfg = zigbee.APSFrameConfig();

% 1. Frame Control (first octet)
frameControl = appFrameBin(1:8);
cfg = decodeFrameControl(cfg, frameControl);

cnt = 8;

if ~strcmp(cfg.DeliveryMode, 'Group')
  if ~isequal(appFrameBin(1:2), [1 1]) % Interpan Frame type (Smart Energy)
    % 2. Destination endpoint (1 octet)
     if strcmp(cfg.FrameType, 'Data') || ...
      (strcmp(cfg.FrameType, 'Acknowledgment') && strcmp(cfg.AcknowledgmentType, 'Data') )
        cfg.DestinationEndpoint = dec2hex(bit2int(appFrameBin(cnt+1 : cnt+8)', 8, false), 2);
        cnt = cnt + 8;
     end
  end
else
  % 3. Group address (2 octets)
  cfg.GroupAddress = dec2hex(bit2int(appFrameBin(cnt+1 : cnt + 2*8), 2*8, false), 4);
  cnt = cnt + 2*8;
end

if strcmp(cfg.FrameType, 'Data') || ...
  (strcmp(cfg.FrameType, 'Acknowledgment') && strcmp(cfg.AcknowledgmentType, 'Data') )
  % 4. Cluster identifier
  cfg.ClusterID = dec2hex(bit2int(appFrameBin(cnt+1 : cnt + 2*8)', 2*8, false), 4);
  cnt = cnt + 2*8;
  
  % 5. Profile identifier
  cfg.ProfileID = dec2hex(bit2int(appFrameBin(cnt+1 : cnt + 2*8)', 2*8, false), 4);
  cnt = cnt + 2*8;
  
  % 6. Source endpoint (1 octet)
  cfg.SourceEndpoint = dec2hex(bit2int(appFrameBin(cnt+1 : cnt+8)', 8, false), 2);
  cnt = cnt + 8;
end

% 7. APS counter (1 octet)
cfg.APSCounter = bit2int(appFrameBin(cnt+1 : cnt+8)', 8, false);
cnt = cnt + 8;

% 8. Extended header
if cfg.ExtendedHeader
  if strcmp(cfg.FrameType, 'Command')
    warning(message('lrwpan:ZigBee:ExtendedHeaderCommand'));
  end
  [cfg, cnt] = decodeExtendedHeader(cfg, appFrameBin, cnt);
end

% 9. Security auxiliary header
if cfg.Security
  [cfg, cnt] = zigbee.internal.decodeSecurityHeader(cfg, appFrameBin, cnt);
end


% 10. Frame payload
if strcmp(cfg.FrameType, 'Command')
  % 10.1 Command type
  cfg = decodeCommandID(cfg, appFrameBin(cnt+1:cnt+8));
  cnt = cnt + 8;
end
% 10.2 Payload
varargout{1} = appFrame(cnt/8+1:end, :);




function cfg = decodeFrameControl(cfg, frameControl)

% 1. Frame type
switch bit2int(frameControl(1:2)', 2, false)
  case 0
    cfg.FrameType = 'Data';  
  case 1
    cfg.FrameType = 'Command';
  case 2
    cfg.FrameType = 'Acknowledgment';
  otherwise
    warning(message('lrwpan:ZigBee:InvalidAPPFrameType'));
end

% 2. Delivery mode
deliveryMode = bit2int(frameControl(3:4)', 2, false);
switch deliveryMode 
  case 0
    cfg.DeliveryMode = 'Unicast';
  case 1
    cfg.DeliveryMode = 'Indirect';
  case 2
    cfg.DeliveryMode = 'Broadcast';
  case 3
    cfg.DeliveryMode = 'Group';
end

% 3. Ack format
if frameControl(5)
  cfg.AcknowledgmentType = 'Command';
else
  cfg.AcknowledgmentType = 'Data';
end

% 4. Security
cfg.Security = logical(frameControl(6));

% 5. Acknowledgment request
cfg.AcknowledgmentRequest = logical(frameControl(7));

% 6. Extended header
cfg.ExtendedHeader = logical(frameControl(8));



function [cfg, cnt] = decodeExtendedHeader(cfg, appFrame, cnt)

% 1. Extended frame control (1 octet)
% 1a. Fragmentation
switch bit2int(appFrame(cnt+1:cnt+2)', 2, false)
  case 0
    cfg.FragmentationStatus = 'No fragmentation';
  case 1
    cfg.FragmentationStatus = 'First fragment';
  case 2
    cfg.FragmentationStatus = 'Subsequent fragment';
  otherwise
    warning(message('lrwpan:ZigBee:InvalidFragmentationStatus'));
end

if any(appFrame(cnt+3 : cnt+8))
  warning(message('lrwpan:ZigBee:InvalidReservedBits'));
end

cnt = cnt+8;

if ~strcmp(cfg.FragmentationStatus, 'No fragmentation')
  
  % 2. Block number (1 octet)
  cfg.BlockNumber = bit2int(appFrame(cnt+1 : cnt+8)', 8, false);
  cnt = cnt+8;

  if strcmp(cfg.FrameType, 'Acknowledgment')
    % 3. Ack bitfield (1 octet)
    cfg.ACKBitField = dec2hex(bit2int(appFrame(cnt+1 : cnt+8)', 8, false), 2);
    cnt = cnt+8;
  end
end


function cfg = decodeCommandID(cfg, bits)

ID = bit2int(bits(:), length(bits), false);
switch ID
  case {1, 2, 3, 4}
    cfg.NetworkCommand = 'Key establishment';
  case 5
    cfg.NetworkCommand = 'Transport key';
	case 6
    cfg.NetworkCommand = 'Update device';
  case 7
    cfg.NetworkCommand = 'Remove device';
  case 8
    cfg.NetworkCommand = 'Request key';
	case 9
    cfg.NetworkCommand = 'Switch key';
	case {10, 11, 12, 13}
    cfg.NetworkCommand = 'Entity authentication';
  case 14
    cfg.NetworkCommand = 'Tunnel';
  otherwise
    warning(message('lrwpan:ZigBee:InvalidAPPCommand', ID));
end
