function [status, cfgLLData, llPayload] = bleLLDataChannelPDUDecode(pdu, crcInit, varargin)
%bleLLDataChannelPDUDecode Decode Bluetooth LE LL data channel PDU
%   [STATUS, CFGLLDATA, LLPAYLOAD] = bleLLDataChannelPDUDecode(DATALLPDU,
%   CRCINIT) decodes the specified Bluetooth(R) low energy (LE) link layer
%   (LL) data channel protocol data unit (PDU), DATALLPDU, returning the
%   decoding status, STATUS, the Bluetooth LE LL data channel PDU
%   configuration object, CFGLLDATA, and the upper-layer payload,
%   LLPAYLOAD. The CRCINIT denotes the initialization value of cyclic
%   redundancy check (CRC).
%
%   STATUS is the result of LL data channel PDU decoding, returned as an
%   enumeration of type <a
%   href="matlab:help('blePacketDecodeStatus')">blePacketDecodeStatus</a>.
%   Any value of STATUS other than 'Success' indicates that the PDU
%   decoding has failed and LLPAYLOAD may not contain valid values. For
%   failed decoding, CFGLLDATA displays no output.
%
%   CFGLLDATA is the decoded data channel PDU configuration, returned as a
%   <a
%   href="matlab:help('bleLLDataChannelPDUConfig')">bleLLDataChannelPDUConfig</a>
%   object.
%
%   LLPAYLOAD is the upper-layer payload, returned as a character array.
%   Each row in this array is the hexadecimal representation of an octet.
%
%   DATALLPDU is a Bluetooth LE LL data channel PDU, specified as one of
%   these types:
%       * Character vector - This vector represents octets in hexadecimal
%         format.
%       * String scalar - This scalar represents octets in hexadecimal
%         format.
%       * Numeric vector of elements in the range [0,255] - This vector
%         represents octets in decimal format.
%       * n-by-2 character array - Each row represents an octet in
%         hexadecimal format. n represents total number of rows.
%       * Binary vector - This vector represents Bluetooth LE LL data
%         channel PDU bits.
%
%   CRCINIT is the CRC Initialization value, specified as a 6-element
%   character vector or a string scalar representing 3-octet hexadecimal
%   value.
%
%   [STATUS, CFGLLDATA, LLPAYLOAD] = bleLLDataChannelPDUDecode(..., Name,
%   Value) specifies additional name-value pair arguments described below.
%   When a name-value pair is not specified, its default value is used.
%
%   'InputFormat'     Bluetooth LE LL data channel PDU format, specified as
%                     'bits' or 'octets'. When specified as 'bits', this
%                     input is a binary vector. When specified as 'octets',
%                     this input is a numeric vector representing octets in
%                     decimal format or a character array or a string
%                     scalar representing octets in hexadecimal format. The
%                     default value is 'bits'.
%
%   % Example 1:
%   % Decode a Link Layer data channel PDU given in the form of bits.
%
%   % Generate a data PDU with upper-layer payload '030004000A0100'.
%   cfgLLData = bleLLDataChannelPDUConfig;
%   cfgLLData.LLID = 'Data (start fragment/complete)';
%   cfgLLData.CRCInitialization = 'ED321C';
%   pdu = bleLLDataChannelPDU(cfgLLData, '030004000A0100');
%
%   crcInit = 'ED321C'; % CRC initialization value
%
%   [status, cfgLLData, llPayload] = bleLLDataChannelPDUDecode(pdu, ...
%                                                              crcInit);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLData
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   % Example 2:
%   % Decode a Link Layer data channel PDU given in the form of octets.
%
%   pdu = '030C00010000060000000A000000FCD2A6'; % Sample PDU
%   crcInit = 'ED323C'; % CRC initialization value
%
%   [status, cfgLLData, llPayload] = bleLLDataChannelPDUDecode(...
%                                                  pdu, crcInit, ...
%                                                  'InputFormat', ...
%                                                  'octets');
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLData.ControlConfig
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   % Example 3:
%   % Decode a corrupted Link Layer data channel PDU.
%
%   pdu = '040C00010000060000000A00'; % Sample PDU
%   crcInit = 'CD3234'; % CRC initialization value
%
%   [status, cfgLLData, llPayload] = bleLLDataChannelPDUDecode(...
%                                                  pdu, crcInit, ...
%                                                  'InputFormat', ...
%                                                  'octets');
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLData
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   See also bleLLDataChannelPDU, bleLLDataChannelPDUConfig,
%   bleLLControlPDUConfig, blePacketDecodeStatus.

%   Copyright 2018-2023 The MathWorks, Inc.

%#codegen

% Validate number of input arguments
narginchk(2, 4);

nvPair = varargin;

% Validate PDU and name-value pair
pduBits = ble.internal.validateLLDecodeInputArguments(pdu, nvPair);

% Validate CRC initialization
ble.internal.validateHex(crcInit, 6, 'CRC Initialization');

% Initialize
cfgLLData = bleLLDataChannelPDUConfig;
cfgLLData.CRCInitialization = crcInit;
cfgLLData.HideDisplay = true;
llPayload = blanks(0);
pos = 1;
octetLen = 8;
doubleOctetLen = 2*octetLen;

% Minimum length required to decode a data channel PDU (2 bytes header, 3
% bytes CRC)
minPDULength = 5;

% The payload field shall be in the range of 0 to 251 octets in length. MIC
% (Message Integrity Check, 4-octets) is currently not supported. Refer
% Bluetooth Core Specification v5.0 | Vol 6, Part B, Section 2.4.
maxPDULength = 251;

% Minimum length validation
if (numel(pduBits)/octetLen) < minPDULength
    status = blePacketDecodeStatus.IncompleteLLDataChannelPDU;
    coder.internal.warning('bluetooth:bleLL:LLDataMinimumLengthRequired');
    return;
end

% Validate CRC (24-bit)
[crcFail, pduBits] = ble.internal.validateCRC(pduBits(1 : end), crcInit);

% Ignore PDU with invalid CRC
if crcFail
    status = blePacketDecodeStatus.CRCFailed;
    return;
end

% Convert to row vector to facilitate binary to decimal conversions
pduBits = pduBits';

% Decode data channel PDU header (2-octets)
[status, cfgLLData, payloadLength] = decodeHeader(cfgLLData, ...
    pduBits(pos : pos+doubleOctetLen-1));
% Header decoding failed
if status ~= blePacketDecodeStatus.Success
    return;
end
pos = pos + doubleOctetLen;

% Validate payload length
if payloadLength ~= (numel(pduBits(pos : end))/octetLen)
    status = blePacketDecodeStatus.LLPDULengthMismatch;
    coder.internal.warning('bluetooth:bleLL:LLPDULengthMismatch');
    return;
end

% MIC is not supported
if payloadLength > maxPDULength
    status = blePacketDecodeStatus.MICNotSupported;
    return;
end

% Link Layer control PDU
if (strcmp(cfgLLData.LLID, 'Control'))
    [status, controlCfg] = decodeLLControlPDU(pduBits(pos:end));
    % Control PDU decoding failed, hide display of the configuration
    if status ~= blePacketDecodeStatus.Success
        return;
    end
    cfgLLData.ControlConfig = controlCfg;
    
% Link Layer data PDU
else
    [status, cfgLLData, llPayload] = decodeLLDataPDU(cfgLLData, pduBits(pos:end));
end
% Decoding is successful. So, reset hide display flag to false
cfgLLData.HideDisplay = false;
end

% Decode data channel PDU header
function [status, cfg, lengthValue] = decodeHeader(cfg, header)
% Initialize
status = blePacketDecodeStatus.Success;
lengthValue = 0;

% LLID (2-bits)
LLID = decodeLLID(header(1:2));
if isempty(LLID)
    status = blePacketDecodeStatus.InvalidLLID;
    coder.internal.warning('bluetooth:bleLL:InvalidLLID');
    return;
end
cfg.LLID = LLID;

% Next expected sequence number (1-bit)
cfg.NESN = header(3);

% Sequence number (1-bit)
cfg.SequenceNumber = header(4);

% More Data (1-bit)
cfg.MoreData = logical(header(5));

% Bit positions 6, 7, 8 are reserved for future use (3-bits)

% Length (8-bits)
lengthValue = bit2int(header(9:16)', 8, 0);
end

% Decode Link Layer data PDU
function [status, cfg, payload] = decodeLLDataPDU(cfg, llPayload)
% Initialize
octetLen = 8;
status = blePacketDecodeStatus.Success;
payload = blanks(0);
lengthValue = numel(llPayload)/octetLen;

% Non-zero payload
if lengthValue > 0
    % Link Layer data payload for upper-layer returned as a character array
    % where each row is the hexadecimal representation of an octet.
    payload = dec2hex(bit2int(reshape(llPayload, octetLen, []), octetLen, 0), 2);
elseif ~strcmp(cfg.LLID, 'Data (continuation fragment/empty)')
    status = blePacketDecodeStatus.ExpectedNonZeroPayload;
    coder.internal.warning('bluetooth:bleLL:ExpectedNonZeroPayload');
    return;
end
end

% Decode Link Layer control PDU
function [status, cfg] = decodeLLControlPDU(controlPayload)
% Initialize
octetLen = 8;
doubleOctetLen = 2*octetLen;
fiveOctetLen = 5*octetLen;
status = blePacketDecodeStatus.Success;

% Initialize control PDU configuration
cfg = bleLLControlPDUConfig;

% Check for minimum control PDU length (1-octet)
if isempty(controlPayload)
    status = blePacketDecodeStatus.IncompleteLLDataChannelPDU;
    return;
end

% Opcode (1-octet)
opcodeRange = 1:8;
opcode = decodeOpcode(controlPayload(opcodeRange));
if isempty(opcode)
    opcodeDec = bit2int(controlPayload(opcodeRange)', octetLen, 0);
    status = blePacketDecodeStatus.UnsupportedLLOpCode;
    cfg.UnknownOpcode = dec2hex(opcodeDec, 2);
    coder.internal.warning('bluetooth:bleLL:UnsupportedLLOpCode', opcodeDec);
    return;
end
cfg.Opcode = opcode;

% Incomplete control data
[isControlDataLengthValid, requiredLength] = ...
    validateControlDataLength(numel(controlPayload)/octetLen, cfg.Opcode);
if ~isControlDataLengthValid
    status = blePacketDecodeStatus.IncompleteLLDataChannelPDU;
    coder.internal.warning('bluetooth:bleLL:IncompleteLLControlData', ...
        cfg.Opcode, requiredLength);
    return;
end

% Control data start index
pos = 9;

switch cfg.Opcode
    case 'Connection update indication'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.4.2.1
        
        % Transmit Window Size (1-octet)
        windowSize = bit2int(controlPayload(pos : pos+octetLen-1)', octetLen, 0);
        if windowSize > 8 || windowSize < 1
            status = blePacketDecodeStatus.InvalidLLWindowSize;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowSize', ...
                windowSize, cfg.Opcode);
            return;
        end
        cfg.WindowSize = windowSize;
        pos = pos + octetLen;
        
        % Transmit Window Offset (2-octets)
        windowOffset = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if windowOffset > 3200
            status = blePacketDecodeStatus.InvalidLLWindowOffset;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowOffset', ...
                windowOffset, cfg.Opcode);
            return;
        end
        cfg.WindowOffset = windowOffset;
        pos = pos + doubleOctetLen;
        
        % Connection Interval (2-octets)
        connInterval = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if connInterval > 3200 || connInterval < 6
            status = blePacketDecodeStatus.InvalidLLConnectionInterval;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionInterval', ...
                connInterval, cfg.Opcode);
            return;
        end
        cfg.ConnectionInterval = connInterval;
        pos = pos + doubleOctetLen;
        
        % Peripheral Latency (2-octets)
        peripheralLatency = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if peripheralLatency > 499
            status = blePacketDecodeStatus.InvalidLLPeripheralLatency;
            coder.internal.warning('bluetooth:bleLL:InvalidLLPeripheralLatency', ...
                peripheralLatency, cfg.Opcode);
            return;
        end
        cfg.PeripheralLatency = peripheralLatency;
        pos = pos + doubleOctetLen;
        
        % Connection Supervision Timeout (2-octets)
        connTimeout = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if connTimeout > 3200 || connTimeout < 10
            status = blePacketDecodeStatus.InvalidLLConnectionTimeout;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionTimeout', ...
                connTimeout, cfg.Opcode);
            return;
        end
        cfg.ConnectionTimeout = connTimeout;
        pos = pos + doubleOctetLen;
        
        % Instant(2-octets)
        cfg.Instant = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        
        % Validate interdependent properties
        % Validate Peripheral latency
        success = validatePeripheralLatency(cfg);
        if ~success
            status = blePacketDecodeStatus.InvalidLLPeripheralLatency;
            coder.internal.warning('bluetooth:bleLL:InvalidLLPeripheralLatency', ...
                peripheralLatency, cfg.Opcode);
            return;
        end
        % Validate connection supervision timeout
        success = validateConnectionTimeout(cfg);
        if ~success
            status = blePacketDecodeStatus.InvalidLLConnectionTimeout;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionTimeout', ...
                connTimeout, cfg.Opcode);
            return;
        end
        % Validate window size
        success = validateWindowSize(cfg);
        if ~success
            status = blePacketDecodeStatus.InvalidLLWindowSize;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowSize', ...
                windowSize, cfg.Opcode);
            return;
        end
        % Validate window offset
        success = validateWindowOffset(cfg);
        if ~success
            status = blePacketDecodeStatus.InvalidLLWindowOffset;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowOffset', ...
                windowOffset, cfg.Opcode);
            return;
        end

    case 'Channel map indication'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.4.2.2
        
        % Channel Map (5-octets)
        bitmap = controlPayload(pos : pos+fiveOctetLen-1);
        % Form 37 element bitmap for position 1 to position 37 (index 0 to
        % 36). Bit positions 38, 39, 40 are reserved for future use
        bitmap = bitmap(1:37);
        if nnz(bitmap) < 2
            status = blePacketDecodeStatus.InvalidLLChannelMap;
            return;
        end
        channelMap = find(bitmap == 1);
        cfg.UsedChannels = channelMap - 1;
        pos = pos+fiveOctetLen;
        
        % Instant (2-octets)
        cfg.Instant = bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);

    case {'Terminate indication', 'Reject indication'}
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.4.2.3 & Section 2.4.2.14
        
        % Error Code (1-octet)
        errorCode = decodeErrorCode(controlPayload(pos : pos+octetLen-1));
        if isempty(errorCode)
            status = blePacketDecodeStatus.InvalidLLErrorCode;
            coder.internal.warning('bluetooth:bleLL:InvalidLLErrorCode', ...
                bit2int(controlPayload(pos : pos+octetLen-1)', octetLen, 0), cfg.Opcode);
            return;
        end
        cfg.ErrorCode = errorCode;

    case 'Unknown response'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.4.2.8
        
        % Unknown type (1-octet)
        unknownOpcode = dec2hex(bit2int(controlPayload(pos : pos+octetLen-1)', octetLen, 0), 2);
        cfg.UnknownOpcode = unknownOpcode;

    case 'Version indication'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.4.2.13
        
        % Version number (1-octet)
        versionNum = decodeVersionCode(controlPayload(pos : pos+octetLen-1));
        if isempty(versionNum)
            status = blePacketDecodeStatus.InvalidBluetoothVersion;
            coder.internal.warning('bluetooth:bleLL:InvalidBluetoothVersion', ...
                bit2int(controlPayload(pos : pos+octetLen-1)', octetLen, 0));
            return;
        end
        cfg.VersionNumber = versionNum;
        pos = pos + octetLen;
        
        % Manufacturer company identifier (2-octets)
        cfg.CompanyIdentifier = dec2hex(bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0), 4);
        pos = pos + doubleOctetLen;
        
        % Sub-version number (2-octets)
        cfg.SubVersionNumber = dec2hex(bit2int(controlPayload(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0), 4);
end
end

% Get LLID description string from received LLID
function llidValue = decodeLLID(llidCode)
% Refer "Table 2.11" in Bluetooth Core Specification v5.0 | Vol 6, Part B

llid = bit2int(llidCode', numel(llidCode), 0);
switch llid
    case 1
        llidValue = 'Data (continuation fragment/empty)';
    case 2
        llidValue = 'Data (start fragment/complete)';
    case 3
        llidValue = 'Control';
    otherwise
        llidValue = '';
end
end

% Get opcode description string from received opcode
function opcodeValue = decodeOpcode(opcode)
% Refer "Table 2.18" in Bluetooth Core Specification v5.0 | Vol 6, Part B

opcode = bit2int(opcode', numel(opcode), 0);
switch opcode
    case 0
        opcodeValue = 'Connection update indication';
    case 1
        opcodeValue = 'Channel map indication';
    case 2
        opcodeValue = 'Terminate indication';
    case 7
        opcodeValue = 'Unknown response';
    case 12
        opcodeValue = 'Version indication';
    case 13
        opcodeValue = 'Reject indication';
    otherwise
        opcodeValue = '';
end
end

% Get error code description string from received error code
function errorCodeValue = decodeErrorCode(errorCode)
% Refer "Table 1.1" in Bluetooth Core Specification v5.0 | Vol 2, Part D

errorCode = bit2int(errorCode', numel(errorCode), 0);
switch errorCode
    case 0
        errorCodeValue = 'Success';
    case 2
        errorCodeValue = 'Unknown connection identifier';
    case 3
        errorCodeValue = 'Hardware failure';
    case 7
        errorCodeValue = 'Memory capacity exceeded';
    case 8
        errorCodeValue = 'Connection timeout';
    case 9
        errorCodeValue = 'Connection limit exceeded';
    case 11
        errorCodeValue = 'Connection already exists';
    case 12
        errorCodeValue = 'Command disallowed';
    case 13
        errorCodeValue = 'Connection rejected due to limited resources';
    case 15
        errorCodeValue = 'Connection rejected due to unacceptable BD_ADDR';
    case 16
        errorCodeValue = 'Connection accept timeout exceeded';
    case 30
        errorCodeValue = 'Unspecified error';
    case 31
        errorCodeValue = 'Invalid LL parameters';
    case 32
        errorCodeValue = 'Unsupported LL parameter value';
    case 33
        errorCodeValue = 'Role change not allowed';
    case 34
        errorCodeValue = 'LL response timeout';
    case 35
        errorCodeValue = 'LL procedure collision';
    case 40
        errorCodeValue = 'Instant passed';
    case 46
        errorCodeValue = 'Channel classification not supported';
    case 54
        errorCodeValue = 'Extended inquiry response too large';
    case 57
        errorCodeValue = 'Advertising timeout';
    case 58
        errorCodeValue = 'Connection rejected due to no suitable channel found';
    case 59
        errorCodeValue = 'Controller busy';
    case 60
        errorCodeValue = 'Unacceptable connection parameters';
    case 62
        errorCodeValue = 'Connection failed to be established';
    case 66
        errorCodeValue = 'Unknown advertising identifier';
    case 67
        errorCodeValue = 'Limit reached';
    case 68
        errorCodeValue = 'Operation cancelled by host';
    otherwise
        errorCodeValue = '';
end
end

% Get version description string from received version code
function version = decodeVersionCode(versionCode)
% Refer Link Layer assigned numbers from Bluetooth SIG

versionCode = bit2int(versionCode', numel(versionCode), 0);
switch versionCode
    case 6
        version = '4.0';
    case 7
        version = '4.1';
    case 8
        version = '4.2';
    case 9
        version = '5.0';
    otherwise
        version = '';
end
end

% Function to verify whether received control data is sufficient or not
function [isValid, requiredLength] = validateControlDataLength(lengthValue, Opcode)
% Initialize to -1 so that we return false when length is 0
requiredLength = -1;
switch Opcode
    case 'Connection update indication'
        requiredLength = 12;
    case 'Channel map indication'
        requiredLength = 8;
    case {'Terminate indication', 'Unknown response', 'Reject indication'}
        requiredLength = 2;
    case 'Version indication'
        requiredLength = 6;
end
isValid = (lengthValue == requiredLength);
end
