function [status, cfgATT] = bleATTPDUDecode(attPDU)
%bleATTPDUDecode Decode Bluetooth LE ATT PDU
%   [STATUS, CFGATT] = bleATTPDUDecode(ATTPDU) decodes the specified
%   Bluetooth(R) low energy (LE) attribute protocol data unit (ATT PDU),
%   returning the corresponding Bluetooth LE ATT PDU configuration object,
%   CFGATT, and the decoding status, STATUS.
%
%   STATUS is the result of an ATT 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 decoding
%   has failed. For failed decoding, CFGATT displays no output.
%
%   CFGATT is the decoded ATT PDU configuration, returned as a <a
%   href="matlab:help('bleATTPDUConfig')">bleATTPDUConfig</a> object.
%
%   ATTPDU is a Bluetooth LE ATT PDU, specified as one of these types:
%       * Character vector - This vector represent octets in hexadecimal
%         format.
%       * String scalar - This scalar represent octets in hexadecimal
%         format.
%       * Numeric vector of elements in the range [0,255] - This vector
%         represent octets in decimal format.
%       * n-by-2 character array - Each row represent an octet in
%         hexadecimal format. n represents number of rows.
%
%   % Example 1:
%   % Decode an ATT PDU.
%
%   % Generate an ATT PDU of type 'Read by type request'.
%   cfgATT = bleATTPDUConfig;
%   cfgATT.Opcode         = 'Read by type request';
%   cfgATT.AttributeType  = '2800';
%   cfgATT.StartHandle    = '0001';
%   cfgATT.EndHandle      = 'FFFF';
%   attPDU = bleATTPDU(cfgATT);
%
%   % Decode an ATT PDU.
%   [status, cfg] = bleATTPDUDecode(attPDU);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfg
%   else
%       fprintf('ATT PDU decoding failed due to ::');
%       status
%   end
%
%   % Example 2:
%   % Decode an ATT PDU.
%
%   % Generate an ATT PDU of type 'Error response'.
%   cfgATT = bleATTPDUConfig('Opcode', 'Error response', ...
%                            'RequestedOpcode', 'Read by type request', ...
%                            'AttributeHandle', '0001', ...
%                            'ErrorMessage', 'Invalid handle');
%   attPDU = bleATTPDU(cfgATT);
%
%   % Decode an ATT PDU.
%   [status, cfg] = bleATTPDUDecode(attPDU);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfg
%   else
%       fprintf('ATT PDU decoding failed due to ::');
%       status
%   end
%
%   % Example 3:
%   % Decode a corrupted ATT PDU.
%
%   attPDU = ['09'; '03'; '01'; '00'; '18'; '0D']; % Sample PDU
%
%   [status, cfgATT] = bleATTPDUDecode(attPDU);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgATT
%   else
%       fprintf('ATT PDU decoding failed due to ::');
%       status
%   end
%
%   See also bleATTPDUConfig, bleATTPDU, blePacketDecodeStatus.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Initialize
cfgATT = bleATTPDUConfig;
cfgATT.HideDisplay = true;
status = blePacketDecodeStatus.Success;
cnt = 1;

% Validate and convert input argument (pdu)
attPDU = ble.internal.convertPayloadToHexArray(attPDU, 'attPDU');

% ATT PDU operation code (1 octet)
opcode = decodeOpCode(attPDU(cnt, :));
if isempty(opcode)
    status = blePacketDecodeStatus.UnsupportedATTOpcode;
    coder.internal.warning('bluetooth:bleATT:UnsupportedATTOpcode', attPDU(cnt, :));
    return;
end
cnt = cnt + 1;

% Calculate PDU length
pduLength = numel(attPDU(cnt : end, :))/2;

% Validate whether the ATT PDU length is sufficient to decode the frame
isValid = validateATTPDULength(opcode, pduLength);
if ~isValid
    status = blePacketDecodeStatus.IncompleteATTPDU;
    coder.internal.warning('bluetooth:bleATT:IncompleteATTPDU');
    return;
end

switch opcode
    case 'Error response'
        % Opcode of a request PDU that caused an error (1 octet)
        requestOpcode = decodeOpCode(attPDU(cnt, :));
        if (~(strcmp(cfgATT.RequestedOpcode_Values, requestOpcode)))
            status = blePacketDecodeStatus.InvalidATTReqOpcodeInErrorResp;
            coder.internal.warning('bluetooth:bleATT:InvalidATTReqOpcodeInErrorResp', attPDU(cnt, :));
            return;
        end
        cfgATT.RequestedOpcode = requestOpcode;
        cnt = cnt + 1;
        
        % Attribute handle in the request PDU that caused an error (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Error code (1 octet)
        errorCode = decodeErrorCode(attPDU(cnt, :));
        if isempty(errorCode)
            status = blePacketDecodeStatus.InvalidATTErrorCode;
            coder.internal.warning('bluetooth:bleATT:InvalidATTErrorCode', ...
                attPDU(cnt, :));
            return;
        end
        cfgATT.ErrorMessage = errorCode;
        
    case {'MTU request', 'MTU response'}
        % Maximum Transmission Unit (MTU) of a peer device (2 octets)
        rxMTU = hex2dec(reshape(attPDU(cnt+1:-1:cnt, :)', 1, []));
        if (rxMTU < 23)
            status = blePacketDecodeStatus.InvalidATTRxMTU;
            coder.internal.warning('bluetooth:bleATT:InvalidATTRxMTU', rxMTU);
            return;
        end
        cfgATT.MaxTransmissionUnit = rxMTU;
        
    case {'Information request', 'Find by type value response'}
        % Starting handle (2 octets)
        startingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Ending handle (2 octets)
        endingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        
        % Validate handles
        if (hex2dec(startingHandle) > hex2dec(endingHandle))
            status = blePacketDecodeStatus.InvalidAttributeHandleRange;
            coder.internal.warning('bluetooth:bleATT:InvalidAttributeHandleRange');
            return;
        end
        cfgATT.StartHandle = startingHandle;
        cfgATT.EndHandle = endingHandle;
        
    case 'Find by type value request'
        % Starting handle (2 octets)
        startingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Ending handle (2 octets)
        endingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Validate handles
        if (hex2dec(startingHandle) > hex2dec(endingHandle))
            status = blePacketDecodeStatus.InvalidAttributeHandleRange;
            coder.internal.warning('bluetooth:bleATT:InvalidAttributeHandleRange');
            return;
        end
        
        % Attribute type (2 octets)
        attributeType = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Attribute value (variable in size)
        if numel(attPDU)/2 > cnt-1
            cfgATT.AttributeValue = attPDU(cnt : end, :);
        end
        cfgATT.StartHandle = startingHandle;
        cfgATT.EndHandle = endingHandle;
        cfgATT.AttributeType = attributeType;
        
    case 'Read request'
        % Attribute handle (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        
    case {'Read response', 'Read blob response'}
        % Attribute value (variable in size)
        cfgATT.AttributeValue = attPDU(cnt : end, :);
        
    case 'Read blob request'
        % Attribute handle (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Attribute value offset (2 octets)
        cfgATT.Offset = hex2dec(reshape(attPDU(cnt+1:-1:cnt, :)', 1, []));
        
    case {'Read by group type request', 'Read by type request'}
        % Starting handle (2 octets)
        startingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Ending handle (2 octets)
        endingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Validate handles
        if (hex2dec(startingHandle) > hex2dec(endingHandle))
            status = blePacketDecodeStatus.InvalidAttributeHandleRange;
            coder.internal.warning('bluetooth:bleATT:InvalidAttributeHandleRange');
            return;
        end
        
        % Attribute type (2 octets or 16 octets)
        attributeType = reshape(attPDU(end:-1:cnt, :)', 1, []);
        attributeLength = numel(attributeType);
        if ((attributeLength ~= 4) && (attributeLength ~= 32))
            status = blePacketDecodeStatus.InvalidAttributeType;
            coder.internal.warning('bluetooth:bleATT:InvalidAttributeType');
            return;
        end
        
        cfgATT.StartHandle = startingHandle;
        cfgATT.EndHandle = endingHandle;
        cfgATT.AttributeType = attributeType;
        
    case {'Write request', 'Write command', 'Handle value notification', ...
            'Handle value indication'}
        % Attribute handle (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Attribute value (variable in size)
        cfgATT.AttributeValue =  attPDU(cnt : end, :);
        
    case {'Write response', 'Execute write response', ...
            'Handle value confirmation'}
        
    case {'Prepare write request', 'Prepare write response'}
        % Attribute handle (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Value offset (2 octets)
        cfgATT.Offset = hex2dec(reshape(attPDU(cnt+1:-1:cnt, :)', 1, []));
        cnt = cnt + 2;
        
        % Attribute value (variable in size)
        cfgATT.AttributeValue =  attPDU(cnt : end, :);
        
    case 'Execute write request'
        % Execute write request flag (1 octet)
        executeWriteFlag = decodeExecuteWriteReqFlag(attPDU(cnt, :));
        if isempty(executeWriteFlag)
            status = blePacketDecodeStatus.InvalidATTExecuteWriteFlag;
            coder.internal.warning('bluetooth:bleATT:InvalidATTExecuteWriteFlag', attPDU(cnt, :));
            return;
        end
        cfgATT.ExecuteWrite = executeWriteFlag;
        
    case 'Read by type response'
        % Length (1 octet)
        attributeDataLength = hex2dec(attPDU(cnt, :));
        cnt = cnt + 1;
        
        if ((pduLength-1) ~= attributeDataLength)
            status = blePacketDecodeStatus.MismatchAttributeDataLength;
            return;
        end
        
        % Attribute data list (handle-value pair)
        % Attribute handle (2 octets)
        cfgATT.AttributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Attribute value (variable in size)
        cfgATT.AttributeValue = attPDU(cnt : (cnt+(attributeDataLength-3)), :);
        
    case 'Read by group type response'
        % Length (1 octet)
        attributeDataLength = hex2dec(attPDU(cnt, :));
        cnt = cnt + 1;
        
        if ((pduLength-1) ~= attributeDataLength)
            status = blePacketDecodeStatus.MismatchAttributeDataLength;
            return;
        end
        
        % Starting handle (2 octets)
        startingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Ending handle (2 octets)
        endingHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Validate handles
        if (hex2dec(startingHandle) > hex2dec(endingHandle))
            status = blePacketDecodeStatus.InvalidAttributeHandleRange;
            coder.internal.warning('bluetooth:bleATT:InvalidAttributeHandleRange');
            return;
        end
        cfgATT.StartHandle = startingHandle;
        cfgATT.EndHandle = endingHandle;
        
        % Attribute value (variable in size)
        cfgATT.AttributeValue = attPDU(cnt : (cnt+(attributeDataLength-5)), :);
        
    case 'Information response'
        % Format (1 octet)
        format = decodeInfoDataFormat(attPDU(cnt, :));
        if isempty(format)
            status = blePacketDecodeStatus.InvalidATTDataFormat;
            coder.internal.warning('bluetooth:bleATT:InvalidATTDataFormat', attPDU(cnt, :));
            return;
        end
        cnt = cnt + 1;
        
        % Information data (list of handle, ID pairs)
        % Attribute handle (2 octets)
        attributeHandle = reshape(attPDU(cnt+1:-1:cnt, :)', 1, []);
        cnt = cnt + 2;
        
        % Attribute type (2 octets or 16 octets)
        attributeType = reshape(attPDU(end:-1:cnt, :)', 1, []);
        
        % Validate information data
        switch format
            case '16 bit'
                if (numel(attributeType)/2 ~= 2)
                    status = blePacketDecodeStatus.InvalidAttributeType;
                    coder.internal.warning('bluetooth:bleATT:InvalidAttributeType');
                    return;
                end
            otherwise
                if (numel(attributeType)/2 ~= 16)
                    status = blePacketDecodeStatus.InvalidAttributeType;
                    coder.internal.warning('bluetooth:bleATT:InvalidAttributeType');
                    return;
                end
        end
        cfgATT.Format = format;
        cfgATT.AttributeHandle = attributeHandle;
        cfgATT.AttributeType = attributeType;
end
cfgATT.Opcode = opcode;
% Enable the display after successfully decoding the input L2CAPFRAME
cfgATT.HideDisplay = false;
end

% Decode execute write flag
function flag = decodeExecuteWriteReqFlag(flagValue)
% Refer 'Table 3.32' in Bluetooth Core Specification, v5.0 | Vol 3, Part F,
% Section 3.4.6.3.
switch flagValue
    case '00'
        flag = 'Cancel all prepared writes';
    case '01'
        flag = 'Write all pending requests';
    otherwise
        flag  = '';
end
end

% Decode Format
function dataFormat = decodeInfoDataFormat(format)
% Refer 'Table 3.8' in Bluetooth Core Specification, v5.0 | Vol 3, Part F,
% Section 3.4.3.2.
switch format
    case '01'
        dataFormat = '16 bit';
    case '02'
        dataFormat = '128 bit';
    otherwise
        dataFormat  = '';
end
end

% Decode OpCode description string from the given OpCode
function opCodeValue = decodeOpCode(opCode)
% Refer Bluetooth Core Specification, v5.0 | Vol 3, Part F, Section 3.4.
switch opCode
    case '01'
        opCodeValue = 'Error response';
    case '02'
        opCodeValue = 'MTU request';
    case '03'
        opCodeValue = 'MTU response';
    case '04'
        opCodeValue = 'Information request';
    case '06'
        opCodeValue = 'Find by type value request';
    case '08'
        opCodeValue = 'Read by type request';
    case '0A'
        opCodeValue = 'Read request';
    case '0B'
        opCodeValue = 'Read response';
    case '0C'
        opCodeValue = 'Read blob request';
    case '0D'
        opCodeValue = 'Read blob response';
    case '10'
        opCodeValue = 'Read by group type request';
    case '12'
        opCodeValue = 'Write request';
    case '13'
        opCodeValue = 'Write response';
    case '52'
        opCodeValue = 'Write command';
    case '16'
        opCodeValue = 'Prepare write request';
    case '17'
        opCodeValue = 'Prepare write response';
    case '18'
        opCodeValue = 'Execute write request';
    case '19'
        opCodeValue = 'Execute write response';
    case '1B'
        opCodeValue = 'Handle value notification';
    case '1D'
        opCodeValue = 'Handle value indication';
    case '1E'
        opCodeValue = 'Handle value confirmation';
    case '05'
        opCodeValue = 'Information response';
    case '07'
        opCodeValue = 'Find by type value response';
    case '09'
        opCodeValue = 'Read by type response';
    case '11'
        opCodeValue = 'Read by group type response';
    otherwise
        opCodeValue = '';
end
end

% Decode ErrorCode description string from the given ErrorCode
function errorCodeValue = decodeErrorCode(errorCode)
% Refer 'Table 3.3' in Bluetooth Core Specification, v5.0 | Vol 3, Part F,
% Section 3.4.1.
switch errorCode
    case '01'
        errorCodeValue = 'Invalid handle';
    case '02'
        errorCodeValue = 'Read not permitted';
    case '03'
        errorCodeValue = 'Write not permitted';
    case '04'
        errorCodeValue = 'Invalid PDU';
    case '05'
        errorCodeValue = 'Insufficient authentication';
    case '06'
        errorCodeValue = 'Request not supported';
    case '07'
        errorCodeValue = 'Invalid offset';
    case '08'
        errorCodeValue = 'Insufficient authorization';
    case '09'
        errorCodeValue = 'Prepare queue full';
    case '0A'
        errorCodeValue = 'Attribute not found';
    case '0B'
        errorCodeValue = 'Attribute not long';
    case '0C'
        errorCodeValue = 'Insufficient encryption key size';
    case '0D'
        errorCodeValue = 'Invalid attribute value length';
    case '0E'
        errorCodeValue = 'Unlikely error';
    case '0F'
        errorCodeValue = 'Insufficient encryption';
    case '10'
        errorCodeValue = 'Unsupported group type';
    case '11'
        errorCodeValue = 'Insufficient resources';
    otherwise
        errorCodeValue = '';
end
end

% Validate whether the ATT PDU length is sufficient to decode the frame
function isValid = validateATTPDULength(opCode, pduLength)
isValid = false;
switch opCode
    case {'Error response', 'Information request', ...
            'Find by type value response', 'Read blob request'}
        isValid = (pduLength == 4);
    case {'MTU request', 'MTU response', 'Read request'}
        isValid = (pduLength == 2);
    case {'Find by type value request', 'Read by group type request', ...
            'Read by type request'}
        isValid = (pduLength >= 6);
    case {'Write request', 'Write command', 'Handle value notification', ...
            'Handle value indication'}
        isValid = (pduLength >= 2);
    case {'Prepare write request', 'Prepare write response'}
        isValid = (pduLength >= 4);
    case 'Execute write request'
        isValid = (pduLength == 1);
    case 'Read by type response'
        isValid = (pduLength >= 3);
    case {'Read by group type response', 'Information response'}
        isValid = (pduLength >= 5);
    case {'Read response', 'Read blob response', 'Write response', ...
            'Execute write response', 'Handle value confirmation'}
        isValid = true;
end
end
