function l2capFrame = bleL2CAPFrame(cfgL2CAP, sdu)
%bleL2CAPFrame Generate Bluetooth LE L2CAP frame
%   L2CAPFRAME = bleL2CAPFrame(CFGL2CAP) generates a Bluetooth(R) low
%   energy (LE) logical link control and adaptation protocol (L2CAP) frame,
%   L2CAPFRAME, for a given Bluetooth LE L2CAP configuration object,
%   CFGL2CAP. Use this syntax to generate the signaling frames.
%
%   L2CAPFRAME = bleL2CAPFrame(CFGL2CAP, SDU) generates a Bluetooth LE
%   L2CAP data frame, L2CAPFRAME, corresponding to the configuration object
%   CFGL2CAP. SDU contains the payload received from the upper-layer
%   protocols such as ATT (Attribute Protocol), SMP (Security Manager
%   Protocol). Use this syntax to generate data frames.
%
%   L2CAPFRAME is the generated Bluetooth LE L2CAP frame, returned as a
%   character array. Each row in this array is the hexadecimal
%   representation of an octet. This value represents the output Bluetooth
%   LE L2CAP frame.
%
%   CFGL2CAP is a Bluetooth LE L2CAP configuration object, specified as a
%   <a href="matlab:help('bleL2CAPFrameConfig')">bleL2CAPFrameConfig</a>
%   object.
%
%   SDU is the upper-layer payload, 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 the
%         hexadecimal format.
%
%   % Example 1:
%   % 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)
%
%   % Example 2:
%   % 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")
%
%   % Example 3:
%   % Generate an L2CAP data frame (LE-frame) with SDU as '0A01E2D3'.
%
%   cfgL2CAP = bleL2CAPFrameConfig;
%   cfgL2CAP.ChannelIdentifier = '007A'; % Dynamic channel identifier
%   l2capFrame = bleL2CAPFrame(cfgL2CAP, ['0A'; '01'; 'E2'; 'D3'])
%
%   See also bleL2CAPFrameConfig, bleL2CAPFrameDecode.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

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

% Validate input argument CFGL2CAP
validateattributes(cfgL2CAP, {'bleL2CAPFrameConfig'}, {'scalar'}, ...
    mfilename, 'cfgL2CAP', 1);

% Validate input argument SDU
if (nargin == 1)
    informationPayload = '';
else
    if isempty(sdu)
        informationPayload = '';
    else
        informationPayload = ble.internal.convertPayloadToHexArray(sdu, 'SDU');
    end
end

% Validate channel identifier
% The '0000' is a null channel identifier, but this is allowed only for the
% command type 'Command reject' with the reason 'Invalid CID in request'
% Refer Bluetooth Core Specification, v5.0 | Vol 3, Part A, Section 4.1.
if ~strcmp(cfgL2CAP.CommandType, 'Command reject') || ...
        ~strcmp(cfgL2CAP.CommandRejectReason, 'Invalid CID in request')
    validateChannelIdentifier(cfgL2CAP, cfgL2CAP.SourceChannelIdentifier, ...
        'SourceChannelIdentifier');
    validateChannelIdentifier(cfgL2CAP, cfgL2CAP.DestinationChannelIdentifier, ...
        'DestinationChannelIdentifier');
end

% Initialize
data = '';
signalData = '';

% Signaling command frames
if strcmp(cfgL2CAP.ChannelIdentifier, cfgL2CAP.SignalingCID)
    % Channel identifier (2 octets)
    channelID = convertToOctets(cfgL2CAP.ChannelIdentifier);
    
    % Signal/Command code (1 octet)
    commandCode = generateCommandTypeCode(cfgL2CAP.CommandType);
    
    % Identifier (1 octet)
    identifier = cfgL2CAP.SignalIdentifier;
    
    switch cfgL2CAP.CommandType
        case 'Command reject'
            % Reason (2 octets)
            reason = convertToOctets(generateCommandRejectReasonCode(...
                cfgL2CAP.CommandRejectReason));
            
            if strcmp(cfgL2CAP.CommandRejectReason, 'Signaling MTU exceeded')
                % Data (2 octets)
                % For the reject reason 'Signaling MTU exceeded', the data
                % value should be current MTU (Maximum Transmission Unit).
                data = convertToOctets(dec2hex(cfgL2CAP.MaxTransmissionUnit, 4));
                
            elseif strcmp(cfgL2CAP.CommandRejectReason, 'Invalid CID in request')
                % Data (4 octets)
                % For the reject reason 'Invalid CID in request', the data
                % value shall contain the source and destination channel
                % endpoints (CIDs) from the rejected command. If the
                % rejected command contains only one of the channel
                % endpoint, the other one shall be replaced by the null CID
                % '0000'.
                data = convertToOctets([cfgL2CAP.DestinationChannelIdentifier ...
                    cfgL2CAP.SourceChannelIdentifier]);
            end
            
            % Put signal command bytes together
            signalData = [reason; data];

        case {'Disconnection request', 'Disconnection response'}
            % Destination channel identifier (2 octets)
            destinationCID = convertToOctets(cfgL2CAP.DestinationChannelIdentifier);
            
            % Source channel identifier (2 octets)
            sourceCID = convertToOctets(cfgL2CAP.SourceChannelIdentifier);
            
            % Put signal command bytes together
            signalData = [destinationCID; sourceCID];

        case 'Connection parameter update request'
            connectionTimeout = cfgL2CAP.ConnectionTimeout * 10;
            connectionIntervalMax = cfgL2CAP.ConnectionIntervalRange(2) * 1.25;
            
            % Validation of Peripheral latency
            coder.internal.errorIf((cfgL2CAP.PeripheralLatency > ....
                ((connectionTimeout / connectionIntervalMax*2)-1)), ...
                'bluetooth:bleL2CAP:InvalidL2CAPPeripheralLatency');
            
            % Minimum connection interval (2 octets)
            intervalMin = convertToOctets(dec2hex(cfgL2CAP.ConnectionIntervalRange(1), 4));
            
            % Maximum connection interval (2 octets)
            intervalMax = convertToOctets(dec2hex(cfgL2CAP.ConnectionIntervalRange(2), 4));
            
            % Peripheral latency (2 octets)
            peripheralLatency = convertToOctets(dec2hex(cfgL2CAP.PeripheralLatency, 4));
            
            % Connection timeout (2 octets)
            connectionTimeout = convertToOctets(...
                dec2hex(cfgL2CAP.ConnectionTimeout, 4));
            
            % Put signal command bytes together
            signalData = [intervalMin; intervalMax; peripheralLatency; ...
                connectionTimeout];

        case 'Connection parameter update response'
            % Result (2 octets)
            result = convertToOctets(...
                generateparameterUpdateResultCode(cfgL2CAP.ParameterUpdateResult));
            
            % Put signal command bytes together
            signalData = result;

        case 'Credit based connection request'
            % LE PSM (Protocol/Service Multiplexer) (2 octets)
            psm = convertToOctets(cfgL2CAP.LEPSM);
            
            % Source channel identifier (2 octets)
            sourceCID = convertToOctets(cfgL2CAP.SourceChannelIdentifier);
            
            % Maximum transmission unit (2 octets)
            mtu = convertToOctets(dec2hex(cfgL2CAP.MaxTransmissionUnit, 4));
            
            % Maximum PDU payload size (2 octets)
            mps = convertToOctets(dec2hex(cfgL2CAP.MaxPDUPayloadSize, 4));
            
            % Initial credits (2 octets)
            credits = convertToOctets(dec2hex(cfgL2CAP.Credits, 4));
            
            % Put signal command bytes together
            signalData = [psm; sourceCID; mtu; mps; credits];

        case 'Credit based connection response'
            % Destination channel identifier (2 octets)
            destinationCID = convertToOctets(cfgL2CAP.DestinationChannelIdentifier);
            
            % Maximum transmission unit (2 octets)
            mtu = convertToOctets(dec2hex(cfgL2CAP.MaxTransmissionUnit, 4));
            
            % Maximum PDU payload size (2 octets)
            mps = convertToOctets(dec2hex(cfgL2CAP.MaxPDUPayloadSize, 4));
            
            % Initial credits (2 octets)
            credits = convertToOctets(dec2hex(cfgL2CAP.Credits, 4));
            
            % Result (2 octets)
            result = convertToOctets(generateconnectionResultCode(...
                cfgL2CAP.ConnectionResult));
            
            % Put signal command bytes together
            signalData = [destinationCID; mtu; mps; credits; result];

        case 'Flow control credit'
            % Channel identifier (2 octets)
            sourceCID = convertToOctets(cfgL2CAP.SourceChannelIdentifier);
            
            % For command type 'Flow control credit', zero (0) credits not
            % allowed as per Bluetooth Core Specification v5.0 | Vol 3,
            % PART-A, Section 4.24
            coder.internal.errorIf((cfgL2CAP.Credits == 0), ...
                'bluetooth:bleL2CAP:InvalidLECredits');
            
            % Credits (2 octets)
            credits = convertToOctets(dec2hex(cfgL2CAP.Credits, 4));
            
            % Put signal command bytes together
            signalData = [sourceCID; credits];
    end
    
    % Signal data length (2 octets)
    signalDataLength = numel(signalData)/2;
    
    % L2CAP frame length (2 octets)
    % Length indicates the size of the information payload in octets,
    % excluding the length of the L2CAP header. Here, 4 octets are for
    % common signaling fields. One octet for code, one octet for identifier
    % and two octets for signal frame length.
    l2capFrameLength = 4 + signalDataLength; % octets
    frameLen = convertToOctets(dec2hex(l2capFrameLength, 4));
    sigLen = convertToOctets(dec2hex(signalDataLength, 4));
    
    % Put it all together:
    l2capFrame = [frameLen; channelID; commandCode; ...
        identifier; sigLen; signalData];

else % Data frames
    % B-frames (Basic frames) for fixed channels (ATT, SMP)
    if any(strcmp(cfgL2CAP.ChannelIdentifier, cfgL2CAP.FixedCIDs))
        % L2CAP frame length (2 octets)
        l2capFrameLength = numel(informationPayload)/2;
        % Validate the data frame length with corresponding channel MTU
        % (Maximum Transmission Unit). MTU specifies the maximum SDU size.
        % MTU is 65535. Refer Bluetooth Core Specification v5.0 | Vol 3,
        % PART A, Section 3.4.
        coder.internal.errorIf((l2capFrameLength > cfgL2CAP.MaxBFrameSDU), ...
            'bluetooth:bleL2CAP:L2CAPChannelMTUExceeded');
        frameLength = convertToOctets(dec2hex(l2capFrameLength, 4));
        
        % Channel identifier (2 octets)
        channelID = convertToOctets(cfgL2CAP.ChannelIdentifier);
        
        % Put it all together: (B-frames)
        l2capFrame = [frameLength; channelID; informationPayload];
        
    % LE-frames (Low Energy frames) for dynamic channels
    % These are specific to application profiles (IPSP, OTP etc.)
    else
        % SDU Length (2 octets)
        % The first LE-frame of the SDU shall contain the L2CAP SDU Length
        % field that shall specify the total number of octets in the SDU.
        % All subsequent LE-frames that are part of the same SDU shall not
        % contain the L2CAP SDU Length field.
        l2capSDULength = numel(informationPayload)/2;
        
        % Validate the data frame length with corresponding channel MTU
        % (Maximum Transmission Unit). MTU specifies the maximum SDU size.
        % MTU is 65533 (As segmentation and reassembly feature is not
        % supported). Refer Bluetooth Core Specification v5.0 | Vol 3, PART
        % A, Section 3.4.
        coder.internal.errorIf((l2capSDULength > cfgL2CAP.MaxLEFrameSDU), ...
            'bluetooth:bleL2CAP:L2CAPChannelMTUExceeded');
        sduLength = convertToOctets(dec2hex(l2capSDULength, 4));
        
        % L2CAP frame length (2 octets)
        % For LE-frames, the Length field includes the octet lengths of the
        % L2CAP SDU Length when present and SDU Payload fields. Without
        % segmentation L2CAP frame length always equals to L2CAP SDU length
        % + 2.
        l2capFrameLength = l2capSDULength + 2;
        frameLength = convertToOctets(dec2hex(l2capFrameLength, 4));
        
        % Channel identifier (2 octets)
        channelID = convertToOctets(cfgL2CAP.ChannelIdentifier);
        
        % Put it all together: (LE-frames)
        l2capFrame = [frameLength; channelID; sduLength; informationPayload];
    end
end
end

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

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

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

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

% Convert the value into octets in LSB first format
function octets = convertToOctets(value)
octets = reshape(value, 2, [])';
octets = octets(end:-1:1, :);
end
