function [status, cfgLLAdv] = bleLLAdvertisingChannelPDUDecode(pdu, varargin)
%bleLLAdvertisingChannelPDUDecode Decode Bluetooth LE LL advertising
%channel PDU
%   [STATUS, CFGLLADV] = bleLLAdvertisingChannelPDUDecode(ADVLLPDU) decodes
%   the specified Bluetooth(R) low energy (LE) link layer (LL) advertising
%   channel protocol data unit (PDU), ADVLLPDU, returning the decoding
%   status, STATUS, and the corresponding Bluetooth LE LL advertising
%   channel PDU configuration object, CFGLLADV.
%
%   STATUS is the result of LL advertising 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 the output CFGLLADV displays no output.
%
%   CFGLLADV is the decoded advertising channel PDU configuration, returned
%   as a <a
%   href="matlab:help('bleLLAdvertisingChannelPDUConfig')">bleLLAdvertisingChannelPDUConfig</a>
%   object.
%
%   ADVLLPDU is a Bluetooth LE LL advertising 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 number of rows.
%       * Binary vector - This vector represents the Bluetooth LE LL
%         advertising channel PDU bits.
%
%   [STATUS, CFGLLADV] = bleLLAdvertisingChannelPDUDecode(..., 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 advertising 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 the 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 advertising channel PDU given in the form of
%   % bits.
%
%   % Generate an 'Advertising indication' PDU with advertising data
%   % '020106'.
%   cfgLLAdv = bleLLAdvertisingChannelPDUConfig;
%   cfgLLAdv.PDUType         = 'Advertising indication';
%   cfgLLAdv.AdvertisingData = '020106';
%   pdu = bleLLAdvertisingChannelPDU(cfgLLAdv);
%
%   % Decode the generated PDU.
%   [status, cfgLLAdv] = bleLLAdvertisingChannelPDUDecode(pdu);
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLAdv
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   % Example 2:
%   % Decode a Link Layer advertising channel PDU given in the form of
%   % octets.
%
%   pdu = 'C409AB8967452301020106A8F1DF'; % Sample PDU
%
%   [status, cfgLLAdv] = bleLLAdvertisingChannelPDUDecode(pdu, ...
%                                                 'InputFormat', 'octets');
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLAdv
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   % Example 3:
%   % Decode a corrupted Link Layer advertising channel PDU.
%
%   pdu = 'D409AB89674523010201'; % Sample PDU
%
%   [status, cfgLLAdv] = bleLLAdvertisingChannelPDUDecode(pdu, ...
%                                                 'InputFormat', 'octets')
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgLLAdv
%   else
%       fprintf('Link Layer decoding status ::');
%       status
%   end
%
%   See also bleLLAdvertisingChannelPDU, bleLLAdvertisingChannelPDUConfig,
%   blePacketDecodeStatus.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Validate number of input arguments
narginchk(1, 3);

nvPair = varargin;

% Validate input arguments
pduBits = ble.internal.validateLLDecodeInputArguments(pdu, nvPair);

% Initialize
cfgLLAdv = bleLLAdvertisingChannelPDUConfig;
cfgLLAdv.HideDisplay = true;
pos = 1;
octetLen = 8;
doubleOctetLen = 2*octetLen;
tripleOctetLen  = 3*octetLen;
fourOctetLen = 4*octetLen;
fiveOctetLen = 5*octetLen;
sixOctetLen = 6*octetLen;
defaultCRCInit = '555555'; % Default CRC initialization value for advertising channel PDUs

% Minimum length required to decode an advertising channel PDU (2 octets
% header, 1 octet payload, 3 octets CRC)
minPDULength = 6;

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

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

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

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

% Decode advertising channel PDU header (2-octets)
[status, cfgLLAdv, payloadLength] = decodeHeader(...
    cfgLLAdv, pduBits(pos : pos+doubleOctetLen));
% 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

% Incomplete advertising channel PDU payload
[isPDULengthValid, requiredLength] = validatePDULength(payloadLength, cfgLLAdv.PDUType);
if ~isPDULengthValid
    status = blePacketDecodeStatus.IncompleteLLAdvertisingChannelPDU;
    coder.internal.warning('bluetooth:bleLL:IncompleteLLAdvertisingChannelPDU', ...
        cfgLLAdv.PDUType, requiredLength);
    return;
end

switch (cfgLLAdv.PDUType)
    case {'Advertising indication', 'Advertising scannable indication', ...
            'Advertising non connectable indication'}
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.3.1.1 & Section 2.3.1.3 & Section 2.3.1.4
        
        % Advertiser device address (6-octets)
        cfgLLAdv.AdvertiserAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Advertising data (variable [0, 31] octets])
        advDataBits = pduBits(...
            pos : (pos+((payloadLength*octetLen)- sixOctetLen)-1));
        % Advertising data is non-empty
        if ~isempty(advDataBits)
            advertisingData = dec2hex(bit2int(reshape(advDataBits, octetLen, []), ...
                octetLen, 0), 2);
            if (numel(advertisingData)/2) > 31
                status = blePacketDecodeStatus.InvalidLLAdvertisingDataLength;
                coder.internal.warning('bluetooth:bleLL:InvalidGAPDataLength', ...
                    'Advertising data');
                return;
            end
        else
            % Advertising data is empty
            advertisingData = blanks(0);
        end
        cfgLLAdv.AdvertisingData = advertisingData;
        
    case 'Advertising direct indication'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.3.1.2
        
        % Advertiser device address (6-octets)
        cfgLLAdv.AdvertiserAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Target device Address (6-octets)
        cfgLLAdv.TargetAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        
    case 'Scan request'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.3.2.1
        
        % Scanner device address (6-octets)
        cfgLLAdv.ScannerAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Advertiser device address (6-octets)
        cfgLLAdv.AdvertiserAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        
    case 'Scan response'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.3.2.2
        
        % Advertiser device address (6-octets)
        cfgLLAdv.AdvertiserAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Scan response data (variable [0, 31] octets])
        scanRspDataBits = pduBits(...
            pos : (pos+((payloadLength*octetLen)-sixOctetLen)-1));
        % Scan response data is non-empty
        if ~isempty(scanRspDataBits)
            scanRspData = dec2hex(...
                bit2int(reshape(scanRspDataBits, octetLen, []), octetLen, 0), 2);
            if (numel(scanRspData)/2) > 31
                status = blePacketDecodeStatus.InvalidLLScanResponseDataLength;
                coder.internal.warning('bluetooth:bleLL:InvalidGAPDataLength', ...
                    'Scan response data');
                return;
            end
        else
            % Scan response data is empty
            scanRspData = blanks(0);
        end
        cfgLLAdv.ScanResponseData = scanRspData;
        
    case 'Connection indication'
        % Refer Bluetooth Core Specification v5.0 | Vol 6, Part B, Section
        % 2.3.3.1
        
        % Initiator device address (6-octets)
        cfgLLAdv.InitiatorAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Advertiser device address (6-octets)
        cfgLLAdv.AdvertiserAddress = dec2hex(...
            bit2int(pduBits(pos : pos+sixOctetLen-1)', sixOctetLen, 0), 12);
        pos = pos + sixOctetLen;
        
        % Access address for the connection (4-octets)
        cfgLLAdv.AccessAddress = dec2hex(...
            bit2int(pduBits(pos : pos+fourOctetLen-1)', fourOctetLen, 0), 8);
        pos = pos + fourOctetLen;
        
        % CRC initialization value (3-octets)
        cfgLLAdv.CRCInitialization = dec2hex(...
            bit2int(pduBits(pos : pos+tripleOctetLen-1)', tripleOctetLen, 0), 6);
        pos = pos + tripleOctetLen;
        
        % Transmit window size (1-octet)
        windowSize = bit2int(pduBits(pos : pos+octetLen-1)', octetLen, 0);
        if windowSize > 8 || windowSize < 1
            status = blePacketDecodeStatus.InvalidLLWindowSize;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowSize', ...
                windowSize, cfgLLAdv.PDUType);
            return;
        end
        cfgLLAdv.WindowSize = windowSize;
        pos = pos + octetLen;
        
        % Transmit window offset (2-octets)
        windowOffset = bit2int(pduBits(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if windowOffset > 3200
            status = blePacketDecodeStatus.InvalidLLWindowOffset;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowOffset', ...
                windowOffset, cfgLLAdv.PDUType);
            return;
        end
        cfgLLAdv.WindowOffset = windowOffset;
        pos = pos + doubleOctetLen;
        
        % Connection interval (2-octets)
        connInterval = bit2int(pduBits(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if connInterval > 3200 || connInterval < 6
            status = blePacketDecodeStatus.InvalidLLConnectionInterval;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionInterval', ...
                connInterval, cfgLLAdv.PDUType);
            return;
        end
        cfgLLAdv.ConnectionInterval = connInterval;
        pos = pos + doubleOctetLen;
        
        % Peripheral latency (2-octets)
        peripheralLatency = bit2int(pduBits(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if peripheralLatency > 499
            status = blePacketDecodeStatus.InvalidLLPeripheralLatency;
            coder.internal.warning('bluetooth:bleLL:InvalidLLPeripheralLatency', ...
                peripheralLatency, cfgLLAdv.PDUType);
            return;
        end
        cfgLLAdv.PeripheralLatency = peripheralLatency;
        pos = pos + doubleOctetLen;
        
        % Connection supervision timeout (2-octets)
        connTimeout = bit2int(pduBits(pos : pos+doubleOctetLen-1)', doubleOctetLen, 0);
        if connTimeout > 3200 || connTimeout < 10
            status = blePacketDecodeStatus.InvalidLLConnectionTimeout;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionTimeout', ...
                connTimeout, cfgLLAdv.PDUType);
            return;
        end
        cfgLLAdv.ConnectionTimeout = connTimeout;
        pos = pos + doubleOctetLen;
        
        % Channel map (5-octets)
        bitmap = pduBits(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);
        cfgLLAdv.UsedChannels = channelMap - 1;
        pos = pos+fiveOctetLen;
        
        % Hop increment count (5-bits)
        hop = bit2int(pduBits(pos : pos+4)', 5, 0);
        if hop > 16 || hop < 5
            status = blePacketDecodeStatus.InvalidLLHopIncrement;
            coder.internal.warning('bluetooth:bleLL:InvalidLLHopIncrement', ...
                hop);
            return;
        end
        cfgLLAdv.HopIncrement = hop;
        pos = pos + 5;
        
        % Sleep clock accuracy (3-bits)
        cfgLLAdv.SleepClockAccuracy = decodeSCACode(pduBits(pos : pos+2));
        
        % Validate interdependent properties
        % Validate Peripheral latency
        success = validatePeripheralLatency(cfgLLAdv);
        if ~success
            status = blePacketDecodeStatus.InvalidLLPeripheralLatency;
            coder.internal.warning('bluetooth:bleLL:InvalidLLPeripheralLatency', ...
                peripheralLatency, cfgLLAdv.PDUType);
            return;
        end
        % Validate connection supervision timeout
        success = validateConnectionTimeout(cfgLLAdv);
        if ~success
            status = blePacketDecodeStatus.InvalidLLConnectionTimeout;
            coder.internal.warning('bluetooth:bleLL:InvalidLLConnectionTimeout', ...
                connTimeout, cfgLLAdv.PDUType);
            return;
        end
        % Validate window size
        success = validateWindowSize(cfgLLAdv);
        if ~success
            status = blePacketDecodeStatus.InvalidLLWindowSize;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowSize', ...
                windowSize, cfgLLAdv.PDUType);
            return;
        end
        % Validate window offset
        success = validateWindowOffset(cfgLLAdv);
        if ~success
            status = blePacketDecodeStatus.InvalidLLWindowOffset;
            coder.internal.warning('bluetooth:bleLL:InvalidLLWindowOffset', ...
                windowOffset, cfgLLAdv.PDUType);
            return;
        end
end
% Decoding is successful. So, reset hide display flag to false
cfgLLAdv.HideDisplay = false;
end

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

% PDU Type (4-bits)
pduType = decodePDUCode(header(1:4));
if isempty(pduType)
    status = blePacketDecodeStatus.UnsupportedLLAdvertisingPDUType;
    coder.internal.warning('bluetooth:bleLL:UnsupportedLLAdvertisingPDUType', ...
        bit2int(header(1:4)', 4, 0));
    return;
end
cfg.PDUType = pduType;

% Bit position 5 is RFU (Reserved for future use) (1-bit)

% ChSel (Channel selection) (1-bit)
chanSel = logical(header(6));
if ~any(strcmp(cfg.PDUType, {'Scan request', 'Scan response'}))
    if (chanSel)
        cfg.ChannelSelection = 'Algorithm2';
    else
        cfg.ChannelSelection = 'Algorithm1';
    end
end

% TxAdd (Tx address Type) (1-bit)
txAddrType = logical(header(7));

% RxAdd (Rx address Type) (1-bit)
rxAddrType = logical(header(8));

switch(cfg.PDUType)
    case {'Advertising indication', 'Advertising non connectable indication', ...
            'Advertising scannable indication', 'Scan response'}
        % rxAddrType is reserved
        if (txAddrType)
            cfg.AdvertiserAddressType = 'Random';
        else
            cfg.AdvertiserAddressType = 'Public';
        end
    case 'Advertising direct indication'
        if (txAddrType)
            cfg.AdvertiserAddressType = 'Random';
        else
            cfg.AdvertiserAddressType = 'Public';
        end
        if (rxAddrType)
            cfg.TargetAddressType = 'Random';
        else
            cfg.TargetAddressType = 'Public';
        end
    case 'Scan request'
        if (txAddrType)
            cfg.ScannerAddressType = 'Random';
        else
            cfg.ScannerAddressType = 'Public';
        end
        if (rxAddrType)
            cfg.AdvertiserAddressType = 'Random';
        else
            cfg.AdvertiserAddressType = 'Public';
        end
    case 'Connection indication'
        if (txAddrType)
            cfg.InitiatorAddressType = 'Random';
        else
            cfg.InitiatorAddressType = 'Public';
        end
        if (rxAddrType)
            cfg.AdvertiserAddressType = 'Random';
        else
            cfg.AdvertiserAddressType = 'Public';
        end
end
% Length (8-bits)
lengthValue = bit2int(header(9:16)', 8, 0);
end

% Get PDU description string from given pduCode
function pduType = decodePDUCode(pduCode)
pduCode = bit2int(pduCode', numel(pduCode), 0);
switch pduCode
    case 0
        pduType = 'Advertising indication';
    case 1
        pduType = 'Advertising direct indication';
    case 2
        pduType = 'Advertising non connectable indication';
    case 3
        pduType = 'Scan request';
    case 4
        pduType = 'Scan response';
    case 5
        pduType = 'Connection indication';
    case 6
        pduType = 'Advertising scannable indication';
    otherwise
        pduType = '';
end
end

% Get SCA (Sleep Clock Accuracy) description string from given scaCode
function sca = decodeSCACode(scaCode)
scaCode = bit2int(scaCode', numel(scaCode), 0);
switch scaCode
    case 0
        sca = '251 to 500 ppm';
    case 1
        sca = '151 to 250 ppm';
    case 2
        sca = '101 to 150 ppm';
    case 3
        sca = '76 to 100 ppm';
    case 4
        sca = '51 to 75 ppm';
    case 5
        sca = '31 to 50 ppm';
    case 6
        sca = '21 to 30 ppm';
    otherwise
        sca = '0 to 20 ppm';
end
end

% Function to verify whether received PDU length is valid or not
function [isValid, requiredLength] = validatePDULength(lengthValue, PDUType)
% Initialize to -1 so that we return false when length is 0
requiredLength = -1;
switch PDUType
    case {'Advertising indication', 'Advertising scannable indication', ...
            'Advertising non connectable indication', 'Scan response'}
        requiredLength = 6;
    case {'Advertising direct indication', 'Scan request'}
        requiredLength = 12;
    case 'Connection indication'
        requiredLength = 34;
end
isValid = (lengthValue >= requiredLength);
end
