function [status, cfgL2CAP, sdu] = bleL2CAPFrameDecode(l2capFrame)
%bleL2CAPFrameDecode Decode Bluetooth LE L2CAP frame
%   [STATUS, CFGL2CAP, SDU] = bleL2CAPFrameDecode(L2CAPFRAME) decodes the
%   specified Bluetooth(R) low energy (LE) logical link control and
%   adaptation protocol (L2CAP) frame, L2CAPFRAME. The function returns the
%   decoding status, STATUS, the corresponding Bluetooth LE L2CAP
%   configuration object, CFGL2CAP, and the upper-layer payload service
%   data unit (SDU), SDU.
%
%   STATUS is the result of L2CAP frame 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 SDU may not
%   contain valid value. For failed decoding, CFGL2CAP displays no output.
%
%   CFGL2CAP is the decoded L2CAP frame configuration, returned as a <a
%   href="matlab:help('bleL2CAPFrameConfig')">bleL2CAPFrameConfig</a>
%   object.
%
%   SDU is the upper-layer payload, returned as a character array. Each row
%   in this array is the hexadecimal representation of an octet.
%
%   L2CAPFRAME is a Bluetooth LE L2CAP frame, 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 L2CAP signaling command frame.
%
%   % Generate an L2CAP signaling command frame of type 'Credit based
%   % connection request'.
%   cfgL2CAP = bleL2CAPFrameConfig;
%   cfgL2CAP.ChannelIdentifier  = '0005';
%   cfgL2CAP.CommandType        = 'Credit based connection request';
%   cfgL2CAP.Credits            = 10;
%   cfgL2CAP.SignalIdentifier   = '01';
%   l2capFrame = bleL2CAPFrame(cfgL2CAP);
%
%   % Decode an L2CAP frame.
%   [status, cfgL2CAP] = bleL2CAPFrameDecode(l2capFrame);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgL2CAP
%   else
%       fprintf('L2CAP frame decoding failed due to ::');
%       status
%   end
%
%   % Example 2:
%   % Decode an L2CAP data frame with SDU.
%
%   % Generate an L2CAP data frame (B-frame) with SDU from ATT as "0A0100".
%   cfgL2CAP = bleL2CAPFrameConfig;
%   cfgL2CAP.ChannelIdentifier = '0004'; % Channel identifier for ATT
%   l2capFrame = bleL2CAPFrame(cfgL2CAP, "0A0100");
%
%   % Decode an L2CAP frame.
%   [status, cfgL2CAP, payload] = bleL2CAPFrameDecode(l2capFrame);
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgL2CAP
%       payload
%   else
%       fprintf('L2CAP frame decoding failed due to ::');
%       status
%   end
%
%   % Example 3:
%   % Decode a corrupted L2CAP frame.
%
%   l2capFrame = '090005000107040060005000'; % Sample frame
%   [status, cfgL2CAP] = bleL2CAPFrameDecode(l2capFrame);
%
%   if strcmp(status, 'Success') % Decoding is successful
%       cfgL2CAP
%   else
%       fprintf('L2CAP frame decoding failed due to ::');
%       status
%   end
%
%   See also bleL2CAPFrameConfig, bleL2CAPFrame, blePacketDecodeStatus.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

% Validate and convert input argument L2CAPFRAME
l2capFrameHexArray = ble.internal.convertPayloadToHexArray(l2capFrame, 'l2capFrame');

% Initialize
cfgL2CAP = bleL2CAPFrameConfig;
cfgL2CAP.HideDisplay = true;
cnt = 1;
sdu = blanks(0);
status = blePacketDecodeStatus.Success;

inputFrameLength = numel(l2capFrameHexArray)/2;

% Validate minimum length required to decode L2CAP header
if (inputFrameLength < 4)
    status = blePacketDecodeStatus.IncompleteL2CAPDataFrame;
    coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPDataFrame', ...
        'L2CAP header', 4);
    return;
end

% Decode basic L2CAP header
% L2CAP frame length (2 octets)
l2capFrameLength = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
cnt = cnt + 2;

% Validate the L2CAP frame length field
if (l2capFrameLength ~= inputFrameLength-4)
    status = blePacketDecodeStatus.MismatchL2CAPHeaderLength;
    return;
end

% Channel identifier (2 octets)
channelIdentifier = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
cnt = cnt + 2;

% Validate channel identifier
if ~isValidChannelID(cfgL2CAP, channelIdentifier)
    status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
    coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
    return;
end

% Signaling frame minimum length validation
if strcmp(channelIdentifier, cfgL2CAP.SignalingCID) && (l2capFrameLength < 4)
    status = blePacketDecodeStatus.IncompleteL2CAPSignalFrame;
    coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPSignalFrame', ...
        'command frame', 8);
    return;
end

% LE-frame minimum length validation
if ~any(strcmp(channelIdentifier, cfgL2CAP.FixedCIDs)) && (l2capFrameLength < 2)
    status = blePacketDecodeStatus.IncompleteL2CAPDataFrame;
    coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPDataFrame', ...
        'LE-frame', 6);
    return;
end

% Signaling frames
if strcmp(channelIdentifier, cfgL2CAP.SignalingCID)
    % Signal/Command code (1 octet)
    commandType = decodeCommandTypeCode(l2capFrameHexArray(cnt, :));
    % Validate command type
    if isempty(commandType)
        status = blePacketDecodeStatus.InvalidL2CAPCommand;
        coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPCommand', ...
            l2capFrameHexArray(cnt, :));
        return;
    end
    cnt = cnt + 1;
    
    % Identifier (1 octet)
    signalIdentifier = l2capFrameHexArray(cnt, :);
    % Validate signal identifier
    if strcmp(signalIdentifier, '00')
        status = blePacketDecodeStatus.IllegalL2CAPSignalIdentifier;
        coder.internal.warning('bluetooth:bleL2CAP:IllegalL2CAPSignalIdentifier');
        return;
    end
    cnt = cnt + 1;
    
    % Signal data length (2 octets)
    signalDataLength = hex2dec(...
        reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
    cnt = cnt + 2;
    
    % Validate the L2CAP signal frame length field
    if (signalDataLength ~= (numel(l2capFrameHexArray(cnt : end, :))/2))
        status = blePacketDecodeStatus.MismatchL2CAPSignalFrameLength;
        return;
    end
    
    % Validate the L2CAP signal frame length is sufficient to decode the
    % frame
    [flag, requiredLength] = validateSignalDataLength(signalDataLength, commandType);
    if ~flag
        coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPSignalFrame', ...
            commandType, requiredLength);
        status = blePacketDecodeStatus.IncompleteL2CAPSignalFrame;
        return;
    end
    
    switch commandType
        case 'Command reject'
            % Reason (2 octets)
            reasonCode = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            
            % Validate command reject reason code
            commandRejectReason = decodeCommandRejectReasonCode(reasonCode);
            if isempty(commandRejectReason)
                status = blePacketDecodeStatus.InvalidL2CAPCommandRejectReason;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPCommandRejectReason', ...
                    reasonCode);
                return;
            end
            cfgL2CAP.CommandRejectReason = commandRejectReason;
            cnt = cnt + 2;
            
            if ~strcmp(commandRejectReason, 'Command not understood')
                if strcmp(commandRejectReason, 'Signaling MTU exceeded')
                    % Data (2 octets)
                    % For reject reason 'Signaling MTU exceeded', the data
                    % is current MTU (Maximum Transmission Unit)
                    
                    % Validate minimum length required to decode signaling
                    % frame 'Command reject'
                    if (signalDataLength ~= 4)
                        status = blePacketDecodeStatus.IncompleteL2CAPSignalFrame;
                        coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPSignalFrame', ...
                            commandType, 12);
                        return;
                    end
                    cfgL2CAP.MaxTransmissionUnit = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
                    
                else
                    % Data (4 octets)
                    % For reject reason 'Invalid CID in request' the data
                    % value shall contain the source and destination
                    % channel endpoints (CIDs). These are from the rejected
                    % command. If the rejected command contains only one of
                    % the channel endpoints, the other one shall be
                    % replaced by the null CID '0000'.
                    
                    % Validate minimum length required to decode signaling
                    % frame 'Command reject'
                    if (signalDataLength ~= 6)
                        status = blePacketDecodeStatus.IncompleteL2CAPSignalFrame;
                        coder.internal.warning('bluetooth:bleL2CAP:IncompleteL2CAPSignalFrame', ...
                            commandType, 14);
                        return;
                    end
                    
                    cfgL2CAP.SourceChannelIdentifier = ...
                        reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
                    cnt = cnt + 2;
                    
                    cfgL2CAP.DestinationChannelIdentifier = ...
                        reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
                end
            end

        case {'Disconnection request', 'Disconnection response'}
            % Destination channel identifier (2 octets)
            destinationChannelID = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate channel identifier
            if ~isValidChannelID(cfgL2CAP, destinationChannelID)
                status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
                return;
            end
            cfgL2CAP.DestinationChannelIdentifier = destinationChannelID;
            cnt = cnt + 2;
            
            % Source channel identifier (2 octets)
            sourceChannelID = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate channel identifier
            if ~isValidChannelID(cfgL2CAP, sourceChannelID)
                status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
                return;
            end
            cfgL2CAP.SourceChannelIdentifier = sourceChannelID;

        case 'Connection parameter update request'
            % Maximum connection interval (2 octets)
            intervalMinDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            % Validate connection interval minimum
            if (intervalMinDec < 6) || (intervalMinDec > 3200)
                status = blePacketDecodeStatus.InvalidL2CAPConnectionIntervalMinimum;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPConnectionIntervalMinimum', ...
                    intervalMinDec);
                return;
            end
            cnt = cnt + 2;
            
            % Minimum connection interval (2 octets)
            intervalMaxDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            % Validate connection interval maximum
            if (intervalMaxDec < 6) || (intervalMaxDec > 3200)
                status = blePacketDecodeStatus.InvalidL2CAPConnectionIntervalMaximum;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPConnectionIntervalMaximum', ...
                    intervalMaxDec);
                return;
            end
            cnt = cnt + 2;
            
            % Peripheral latency (2 octets)
            peripheralLatencyDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            cnt = cnt + 2;
            
            % Timeout multiplier (2 octets)
            timeoutMultiplierDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            % Validate connection timeout
            if (timeoutMultiplierDec < 10) || (timeoutMultiplierDec > 3200)
                status = blePacketDecodeStatus.InvalidL2CAPConnectionTimeout;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPConnectionTimeout', ...
                    timeoutMultiplierDec);
                return;
            end
            
            % Validate connection interval range
            if (intervalMinDec > intervalMaxDec)
                status = blePacketDecodeStatus.InvalidL2CAPConnectionIntervalRange;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPConnectionIntervalRange');
                return;
            end
            
            % Validate Peripheral latency
            connSupervisionTimeout = timeoutMultiplierDec * 10;
            connIntervalMax = intervalMaxDec * 1.25;
            if ((peripheralLatencyDec > ((connSupervisionTimeout / (connIntervalMax*2))-1))) || ...
                    (peripheralLatencyDec >= 500)
                status = blePacketDecodeStatus.InvalidL2CAPPeripheralLatency;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPPeripheralLatency');
                return;
            end
            cfgL2CAP.PeripheralLatency = peripheralLatencyDec;
            cfgL2CAP.ConnectionIntervalRange = [intervalMinDec intervalMaxDec];
            cfgL2CAP.ConnectionTimeout = timeoutMultiplierDec;

        case 'Connection parameter update response'
            % Result (2 octets) (about parameters updating)
            % Validate parameter update result code
            parameterUpdateResult = decodeParameterUpdateResultCode(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if isempty(parameterUpdateResult)
                status = blePacketDecodeStatus.InvalidL2CAPParameterUpdateResult;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPParameterUpdateResult', ...
                    reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
                return;
            end
            cfgL2CAP.ParameterUpdateResult = parameterUpdateResult;

        case 'Credit based connection request'
            % LE PSM (Protocol/Service Multiplexer) (2 octets)
            lepsm = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate LE PSM
            psmDec = hex2dec(lepsm);
            if (psmDec == 0) || (psmDec > 255)
                status = blePacketDecodeStatus.InvalidLEPSM;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidLEPSM', ...
                    psmDec);
                return;
            end
            cfgL2CAP.LEPSM = lepsm;
            cnt = cnt + 2;
            
            % Source channel identifier (2 octets)
            sourceChannelID = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate channel identifier
            if ~isValidChannelID(cfgL2CAP, sourceChannelID)
                status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
                return;
            end
            cfgL2CAP.SourceChannelIdentifier = sourceChannelID;
            cnt = cnt + 2;
            
            % Maximum transmission unit (2 octets)
            % Validation of MTU
            mtuDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if (mtuDec < 23)
                status = blePacketDecodeStatus.InvalidL2CAPChannelMTU;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelMTU', ...
                    mtuDec);
                return;
            end
            cfgL2CAP.MaxTransmissionUnit = mtuDec;
            cnt = cnt + 2;
            
            % Maximum PDU payload size (2 octets)
            % Validation of MPS
            mpsDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if (mpsDec < 23) || (mpsDec > cfgL2CAP.MaxLEFrameSDU)
                status = blePacketDecodeStatus.InvalidL2CAPChannelMPS;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelMPS', ...
                    mpsDec);
                return;
            end
            cfgL2CAP.MaxPDUPayloadSize = mpsDec;
            cnt = cnt + 2;
            
            % Initial credits (2 octets)
            cfgL2CAP.Credits = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));

        case 'Credit based connection response'
            % Destination channel identifier (2 octets)
            destinationChannelID = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate channel identifier
            if ~isValidChannelID(cfgL2CAP, destinationChannelID)
                status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
                return;
            end
            cfgL2CAP.DestinationChannelIdentifier = destinationChannelID;
            cnt = cnt + 2;
            
            % Maximum transmission unit (2 octets)
            % Validation of MTU
            mtuDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if (mtuDec < 23)
                status = blePacketDecodeStatus.InvalidL2CAPChannelMTU;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelMTU', ...
                    mtuDec);
                return;
            end
            cfgL2CAP.MaxTransmissionUnit = mtuDec;
            cnt = cnt + 2;
            
            % Maximum PDU payload size (2 octets)
            % Validation of MPS
            mpsDec = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if (mpsDec < 23) || (mpsDec > cfgL2CAP.MaxLEFrameSDU)
                status = blePacketDecodeStatus.InvalidL2CAPChannelMPS;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelMPS', ...
                    mpsDec);
                return;
            end
            cfgL2CAP.MaxPDUPayloadSize = mpsDec;
            cnt = cnt + 2;
            
            % Initial credits (2 octets)
            cfgL2CAP.Credits = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            cnt = cnt + 2;
            
            % Result (2 octets) (about connection)
            % Validate connection result code
            connectionResult = decodeConnectionResultCode(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            if isempty(connectionResult)
                status = blePacketDecodeStatus.InvalidL2CAPConnectionResult;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPConnectionResult', ...
                    reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
                return;
            end
            cfgL2CAP.ConnectionResult = connectionResult;

        case 'Flow control credit'
            % Channel identifier (2 octets)
            sourceChannelID = reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []);
            % Validate channel identifier
            if ~isValidChannelID(cfgL2CAP, sourceChannelID)
                status = blePacketDecodeStatus.InvalidL2CAPChannelIdentifier;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPChannelIdentifier');
                return;
            end
            cfgL2CAP.SourceChannelIdentifier = sourceChannelID;
            cnt = cnt + 2;
            
            % Credits (2 octets)
            creditsDecimal = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
            
            % For command type 'Flow control credit' zero credits not allowed
            % As per Bluetooth core specification v5, Vol 3, Part-A, Section:
            % 4.24
            if (creditsDecimal == 0)
                status = blePacketDecodeStatus.InvalidLECredits;
                coder.internal.warning('bluetooth:bleL2CAP:InvalidLECredits');
                return;
            end
            cfgL2CAP.Credits = creditsDecimal;
    end
    
    cfgL2CAP.CommandType = commandType;
    cfgL2CAP.SignalIdentifier = signalIdentifier;
    
else % Data frames
    % Decode B-frames (Basic frames) for fixed channels (ATT, SMP)
    if any(strcmp(channelIdentifier, cfgL2CAP.FixedCIDs))
        % Decode SDU
        if ~isempty(l2capFrameHexArray(cnt : end, :))
            sdu = l2capFrameHexArray(cnt : end, :);
        end
        
    % Decode LE-frames (Low Energy frames) for dynamic channels
    % These are specific to application profiles (IPSP, OTP etc.)
    else
        % SDU length (2 octets)
        % This field is only present in first LE L2CAP frame of the SDU
        l2CAPSDULength = hex2dec(reshape(l2capFrameHexArray(cnt+1:-1:cnt, :)', 1, []));
        if (l2CAPSDULength > cfgL2CAP.MaxLEFrameSDU)
            status = blePacketDecodeStatus.InvalidL2CAPSDULength;
            coder.internal.warning('bluetooth:bleL2CAP:InvalidL2CAPSDULength', ...
                l2CAPSDULength);
            return;
        end
        cnt = cnt + 2;
        
        % Validate the L2CAP frame SDU Length field
        if (l2CAPSDULength ~= (numel(l2capFrameHexArray(cnt : end, :))/2))
            status = blePacketDecodeStatus.L2CAPSegmentationUnsupported;
            coder.internal.warning('bluetooth:bleL2CAP:L2CAPSegmentationUnsupported');
            return;
        end
        
        % Decode SDU
        if ~isempty(l2capFrameHexArray(cnt : end, :))
            sdu = l2capFrameHexArray(cnt : end, :);
        end
    end
end

cfgL2CAP.ChannelIdentifier = channelIdentifier;

% Enable the display after successfully decoding the input L2CAPFRAME
cfgL2CAP.HideDisplay = false;
end

% Decode commandTypeValue from given commandTypeCode
function commandTypeValue = decodeCommandTypeCode(commandTypeCode)
% Refer 'Table 4.2' in Bluetooth Core Specification, v5.0 | Vol 3, Part A,
% Section 4.
switch commandTypeCode
    case '01'
        commandTypeValue = 'Command reject';
    case '06'
        commandTypeValue = 'Disconnection request';
    case '07'
        commandTypeValue = 'Disconnection response';
    case '12'
        commandTypeValue = 'Connection parameter update request';
    case '13'
        commandTypeValue = 'Connection parameter update response';
    case '14'
        commandTypeValue = 'Credit based connection request';
    case '15'
        commandTypeValue = 'Credit based connection response';
    case '16'
        commandTypeValue = 'Flow control credit';
    otherwise
        commandTypeValue = '';
end
end

% Decode commandRejectReasonValue from given commandRejectReasonCode
function commandRejectReasonValue = decodeCommandRejectReasonCode(commandRejectReasonCode)
% Refer 'Table 4.3' in Bluetooth Core Specification, v5.0 | Vol 3, Part A,
% Section 4.1.
switch commandRejectReasonCode
    case '0000'
        commandRejectReasonValue = 'Command not understood';
    case '0001'
        commandRejectReasonValue = 'Signaling MTU exceeded';
    case '0002'
        commandRejectReasonValue = 'Invalid CID in request';
    otherwise
        commandRejectReasonValue = '';
end
end

% Decode parameterUpdateResultValue from given parameterUpdateResultCode
function parameterUpdateResultValue = decodeParameterUpdateResultCode(parameterUpdateResultCode)
% Refer 'Table 4.18' in Bluetooth Core Specification, v5.0 | Vol 3, Part A,
% Section 4.21.
switch parameterUpdateResultCode
    case '0000'
        parameterUpdateResultValue = 'Accepted';
    case '0001'
        parameterUpdateResultValue = 'Rejected';
    otherwise
        parameterUpdateResultValue = '';
end
end

% Decode connectionResultValue from given connectionResultCode
function connectionResultValue = decodeConnectionResultCode(connectionResultCode)
% Refer 'Table 4.20' in Bluetooth Core Specification, v5.0 | Vol 3, Part A,
% Section 4.23.
switch connectionResultCode
    case '0000'
        connectionResultValue = 'Successful';
    case '0002'
        connectionResultValue = 'LEPSM not supported';
    case '0004'
        connectionResultValue = 'No resources available';
    case '0005'
        connectionResultValue = 'Insufficient authentication';
    case '0006'
        connectionResultValue = 'Insufficient authorization';
    case '0007'
        connectionResultValue = 'Insufficient encryption key size';
    case '0008'
        connectionResultValue = 'Insufficient encryption';
    case '0009'
        connectionResultValue = 'Invalid source CID';
    case '000A'
        connectionResultValue = 'Source CID already allocated';
    case '000B'
        connectionResultValue = 'Unacceptable parameters';
    otherwise
        connectionResultValue = '';
end
end

% Validate channel identifier
function status = isValidChannelID(cfg, channelID)
status = any(strcmp(channelID, cfg.ChannelIdentifiers));
end

% Checks whether the frame has sufficient length to decode
function [status, requiredLength] = validateSignalDataLength(frameLength, type)
status = false;
requiredLength = 0;
commonSignalDataLength = 8;

switch type
    case 'Command reject'
        status = (frameLength >= 2);
        requiredLength = commonSignalDataLength + 2;
    case {'Disconnection request', 'Disconnection response', 'Flow control credit'}
        status = (frameLength == 4);
        requiredLength = commonSignalDataLength + 4;
    case 'Connection parameter update request'
        status = (frameLength == 8);
        requiredLength = commonSignalDataLength + 8;
    case 'Connection parameter update response'
        status = (frameLength == 2);
        requiredLength = commonSignalDataLength + 2;
    case {'Credit based connection request', 'Credit based connection response'}
        status = (frameLength == 10);
        requiredLength = commonSignalDataLength + 10;
end
end
