function [status, cfgGAP] = bleGAPDataBlockDecode(dataBlock)
%bleGAPDataBlockDecode Decode Bluetooth LE GAP data block
%   [STATUS, CFGGAP] = bleGAPDataBlockDecode(DATABLOCK) decodes a
%   Bluetooth(R) low energy (LE) generic access profile (GAP) data block,
%   DATABLOCK, of the type advertising data (AD) or scan response data
%   (SRD), returning the decoding status, STATUS, and the Bluetooth LE GAP
%   data block configuration object, CFGGAP.
%
%   STATUS is the result of GAP data block decoding, returned as an
%   enumeration of type <a
%   href="matlab:help('blePacketDecodeStatus')">blePacketDecodeStatus</a>.
%   Any value of STATUS other than 'Success' indicates that the decoding
%   has failed and CFGGAP displays no output.
%
%   CFGGAP is the decoded GAP data block configuration, returned as a <a
%   href="matlab:help('bleGAPDataBlockConfig')">bleGAPDataBlockConfig</a>
%   object.
%
%   DATABLOCK is a Bluetooth LE GAP data block, 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.
%
%   % Example 1:
%   % Decode an advertising data block.
%
%   % Generate an advertising data (AD) block with the advertising data
%   % types 'Flags' and 'Tx power level'.
%   cfgGAP = bleGAPDataBlockConfig;
%   cfgGAP.AdvertisingDataTypes   = {'Flags'; 'Tx power level'};
%   cfgGAP.LEDiscoverability      = 'Limited';
%   cfgGAP.TxPowerLevel           = 45;
%   dataBlock = bleGAPDataBlock(cfgGAP);
%
%   % Decode an advertising data block.
%   [status, cfgGAP] = bleGAPDataBlockDecode(dataBlock);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgGAP
%   else
%       fprintf('Advertising data block decoding failed due to ::');
%       status
%   end
%
%   % Example 2:
%   % Decode an advertising data block.
%
%   % Generate an advertising data (AD) block with the AD types
%   % 'Advertising interval' and 'Local name'.
%   cfgGAP = bleGAPDataBlockConfig('AdvertisingDataTypes', ...
%                                     {'Advertising interval', ...
%                                      'Local name'});
%   cfgGAP.AdvertisingInterval  = 48;
%   cfgGAP.LocalNameShortening  = true;
%   cfgGAP.LocalName            = 'MathWorks';
%   dataBlock = bleGAPDataBlock(cfgGAP);
%
%   % Decode an advertising data block.
%   [status, cfgGAP] = bleGAPDataBlockDecode(dataBlock);
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgGAP
%   else
%       fprintf('Advertising data block decoding failed due to ::');
%       status
%   end
%
%   % Example 3:
%   % Decode a corrupted advertising data block.
%
%   dataBlock = '010106010202'; % Sample advertising data block
%   [status, cfgGAP] = bleGAPDataBlockDecode(dataBlock);
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgGAP
%   else
%       fprintf('Advertising data block decoding failed due to ::');
%       status
%   end
%
%   See also bleGAPDataBlockConfig, bleGAPDataBlock,
%   blePacketDecodeStatus.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Validate and convert input argument DATABLOCK
dataBlockCharArray = ble.internal.convertPayloadToHexArray(dataBlock, ...
    'dataBlock');

% Initialize
cfgGAP = bleGAPDataBlockConfig;
cfgGAP.HideDisplay = false;
cnt = 1;
advtTypeCnt = 0;
status = blePacketDecodeStatus.Success;

% For codegen, initialize elements in the cell-array with '' (empty
% character)
adTypes = cell(numel(cfgGAP.AdvertisingDataTypes_Values), 1);
for i = 1:numel(adTypes)
    adTypes{i} = blanks(0);
end

while(cnt <= numel(dataBlockCharArray)/2)
    % Length of the single advertising data (1 octet)
    adLength = hex2dec(dataBlockCharArray(cnt, :));
    cnt = cnt + 1;
    
    % Validate advertising data length
    if ((numel(dataBlockCharArray(cnt : end, :))/2) < adLength)
        status = blePacketDecodeStatus.MismatchGAPADLength;
        cfgGAP.HideDisplay = true;
        break;
    end
    
    % Skip advertising data with length 0 (zero)
    if (adLength == 0)
        continue;
    end
    
    % Advertising data type (1 octet)
    [adType, cfgGAP] = decodeADType(dataBlockCharArray(cnt, :), cfgGAP);
    % Validate advertising data type
    if isempty(adType)
        status = blePacketDecodeStatus.UnsupportedGAPADType;
        coder.internal.warning('bluetooth:bleGAP:UnsupportedGAPADType', ...
            dataBlockCharArray(cnt, :));
        cfgGAP.HideDisplay = true;
        break;
    end
    cnt = cnt + 1;
    
    % Validate the GAP advertising data length. Check whether length is
    % sufficient/valid to decode the advertising data based on the
    % advertising data type
    isValid = isValidADLength(adLength, adType);
    if ~isValid
        status = blePacketDecodeStatus.InvalidGAPADLength;
        cfgGAP.HideDisplay = true;
        break;
    end
    
    % Skip advertising data with length 1 (one)
    % Advertising data with length 1 indicates, that it has advertising
    % type only. It doesn't have any advertising data corresponding to that
    % type.
    if (adLength == 1)
        continue;
    end
    
    switch adType
        case 'Flags'
            % Advertising data (Flags) (1 octet)
            cfgGAP = decodeFlags(cfgGAP, dataBlockCharArray(cnt, :));
            cnt = cnt + 1;

        case 'Local name'
            % Advertising data (Local name) (variable octets)
            % Decode LocalName as specified in Supplement to Bluetooth core
            % specification CSSv7, Part-A, Section: 1.2
            localName = char...
                (hex2dec(dataBlockCharArray(cnt:cnt + (adLength-2), :)))';
            cfgGAP.LocalName = localName;
            cnt = cnt + (adLength-1);

        case 'UUIDs'
            % Advertising data (UUIDs) (variable octets)
            uuidOctets = dataBlockCharArray(cnt : (cnt+(adLength-2)), :);
            cfgGAP.Identifiers = getUUIDs(uuidOctets, 16);
            cnt = cnt + (adLength-1);

        case 'Advertising interval'
            % Advertising data (Advertising interval) (2 octets)
            advertisingInterval = hex2dec(...
                reshape(dataBlockCharArray(cnt+1:-1:cnt, :)', 1, []));
            
            % Validate advertising interval
            if (advertisingInterval < 32)
                status = blePacketDecodeStatus.InvalidGAPAdvertisingInterval;
                coder.internal.warning('bluetooth:bleGAP:InvalidGAPAdvertisingInterval', ...
                    advertisingInterval);
                cfgGAP.HideDisplay = true;
                break;
            end
            cfgGAP.AdvertisingInterval = advertisingInterval;
            cnt = cnt + 2;

        case 'Tx power level'
            % Advertising data (Tx power level) (1 octet)
            cfgGAP.TxPowerLevel = double(getInt8Value(...
                hex2dec(dataBlockCharArray(cnt, :))));
            cnt = cnt + 1;

        case 'Connection interval range'
            % Advertising data (Connection interval range) (4 octets)
            % IntervalMin (2 octets)
            intervalMinDec = hex2dec(reshape(dataBlockCharArray(cnt+1:-1:cnt, :)', 1, []));
            cnt = cnt + 2;
            % Validation of interval minimum
            if (intervalMinDec < 6) || (intervalMinDec > 3200)
                status = blePacketDecodeStatus.InvalidGAPConnectionIntervalMinimum;
                coder.internal.warning('bluetooth:bleGAP:InvalidGAPConnectionIntervalMinimum', ...
                    intervalMinDec);
                cfgGAP.HideDisplay = true;
                break;
            end
            
            % IntervalMax (2 octets)
            intervalMaxDec = hex2dec(reshape(dataBlockCharArray(cnt+1:-1:cnt, :)', 1, []));
            cnt = cnt + 2;
            % Validation of interval maximum
            if (intervalMaxDec < 6) || (intervalMaxDec > 3200)
                status = blePacketDecodeStatus.InvalidGAPConnectionIntervalMaximum;
                coder.internal.warning('bluetooth:bleGAP:InvalidGAPConnectionIntervalMaximum', ...
                    intervalMaxDec);
                cfgGAP.HideDisplay = true;
                break;
            end
            
            % Validation of interval minimum and interval maximum
            if (intervalMinDec > intervalMaxDec)
                status = blePacketDecodeStatus.InvalidGAPConnectionIntervalRange;
                coder.internal.warning('bluetooth:bleGAP:InvalidGAPConnectionIntervalRange');
                cfgGAP.HideDisplay = true;
                break;
            end
            cfgGAP.ConnectionIntervalRange = [intervalMinDec intervalMaxDec];
    end
    advtTypeCnt = advtTypeCnt + 1;
    adTypes{advtTypeCnt} = adType;
end
% For codegen
adTypesList = cell(1, advtTypeCnt);
for i = 1:numel(adTypesList)
    adTypesList{i} = adTypes{i};
end
cfgGAP.AdvertisingDataTypes = adTypesList;
end

% Decode advertising data of type 'Flags'
function cfg = decodeFlags(cfg, flags)
flags = int2bit(hex2dec(flags), 8, 0)';

% LE Discoverability
discoverability = bit2int(flags(1:2)', 2, 0);
switch discoverability
    case 1
        cfg.LEDiscoverability = 'Limited';
    case 2
        cfg.LEDiscoverability = 'General';
    case 3
        cfg.LEDiscoverability = 'Limited and general';
    otherwise
        cfg.LEDiscoverability = 'None';
end

% BR/EDR support
if flags(3)
    cfg.BREDR = false;
    cfg.LE = 'None';
else
    cfg.BREDR = true;
    % Simultaneous LE and BR/EDR
    le = bit2int(flags(4:5)', 2, 0);
    switch le
        case 1
            cfg.LE = 'Controller';
        case 2
            cfg.LE = 'Host';
        case 3
            cfg.LE = 'Host and controller';
        otherwise
            cfg.LE = 'None';
    end
end
end

% Get adTypeValue from given adTypeCode
% Refer Advertising Data Type (AD Type) from 'Generic Access Profile' in
% Bluetooth SIG
function [adTypeValue, cfg] = decodeADType(adTypeCode, cfg)
switch adTypeCode
    case '01'
        adTypeValue = 'Flags';
    case '02'
        adTypeValue = 'UUIDs';
        cfg.IdentifierType = 'Incomplete';
    case '03'
        adTypeValue = 'UUIDs';
        cfg.IdentifierType = 'Complete';
    case '08'
        adTypeValue = 'Local name';
        cfg.LocalNameShortening = true;
    case '09'
        adTypeValue = 'Local name';
        cfg.LocalNameShortening = false;
    case '0A'
        adTypeValue = 'Tx power level';
    case '12'
        adTypeValue = 'Connection interval range';
    case '1A'
        adTypeValue = 'Advertising interval';
    otherwise
        adTypeValue = '';
end
end

% Decode service UUIDs
function uuidValues = getUUIDs(uuidOctets, bits)
% Get number of bytes from the bits
noOfBytes = bits/8;
% Initialize the uuidValues with n-by-4 character array. Here 4 is for
% 16-bit UUIDs and 'n' is the number of UUIDs derived from uuidOctets
uuidValues = char(zeros((numel(uuidOctets)/2)/noOfBytes, noOfBytes*2));
index = 1;
% Convert the UUIDs into octets in MSB first format
for i = 1:noOfBytes:(numel(uuidOctets)/2)
    temp = uuidOctets(i : i+(noOfBytes -1), :);
    uuidValues(index, :) = reshape(temp(end:-1:1, :)', 1, []);
    index = index + 1;
end
end

% Get int8 value from the given uint8 value
function int8Value = getInt8Value(uint8Value)
if uint8Value <= 127
    int8Value = uint8Value;
else
    int8Value = uint8Value - 256;
end
end

% Identify whether received advertising data has sufficient length to
% decode or not
function status = isValidADLength(dataLength, type)
status = false;

switch type
    case 'Flags'
        status = ((dataLength == 2) || (dataLength == 1));
    case 'UUIDs'
        status = (rem(dataLength-1, 2) == 0);
    case 'Local name'
        status = true;
    case 'Tx power level'
        status = (dataLength == 2);
    case 'Connection interval range'
        status = (dataLength == 5);
    case 'Advertising interval'
        status = (dataLength == 3);
end
end
