function pdu = bleLLDataChannelPDU(cfgLLData, llPayload)
%bleLLDataChannelPDU Generate Bluetooth LE LL data channel PDU
%   DATALLPDU = bleLLDataChannelPDU(CFGLLDATA) generates a Bluetooth(R) low
%   energy (LE) link layer (LL) data channel protocol data unit (PDU),
%   DATALLPDU, for a given Bluetooth LE LL data channel configuration
%   object, CFGLLDATA. Use this syntax to generate a Bluetooth LE LL
%   control PDU.
%
%   DATALLPDU = bleLLDataChannelPDU(CFGLLDATA, LLPAYLOAD) generates a
%   Bluetooth LE LL data channel PDU, DATALLPDU, containing the upper-layer
%   payload LLPAYLOAD for a given Bluetooth LE LL data channel
%   configuration object, CFGLLDATA. Use this syntax to generate a
%   Bluetooth LE LL data PDU.
%
%   DATALLPDU is the generated Bluetooth LE LL data channel PDU, returned
%   as a binary column vector.
%
%   CFGLLDATA is a Bluetooth LE LL data channel configuration object,
%   specified as a <a
%   href="matlab:help('bleLLDataChannelPDUConfig')">bleLLDataChannelPDUConfig</a>
%   object.
%
%   LLPAYLOAD 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
%         hexadecimal format. n represents total number of rows.
%
%   % Example 1:
%   % Generate a data PDU with upper-layer payload '030004000A0100'.
%
%   cfgLLData = bleLLDataChannelPDUConfig;
%   cfgLLData.LLID = 'Data (start fragment/complete)';
%   pdu = bleLLDataChannelPDU(cfgLLData, '030004000A0100');
%
%   % Example 2:
%   % Generate a control PDU of type 'Connection update indication'.
%
%   cfgControl = bleLLControlPDUConfig;
%   cfgControl.Opcode = 'Connection update indication';
%   cfgLLData = bleLLDataChannelPDUConfig('LLID', 'Control', ...
%                                         'ControlConfig', cfgControl);
%   pdu = bleLLDataChannelPDU(cfgLLData);
%
%   See also bleLLDataChannelPDUDecode, bleLLDataChannelPDUConfig,
%   bleLLControlPDUConfig.

%   Copyright 2018-2021 The MathWorks, Inc.

%#codegen

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

% Validate input argument CFGLLDATA
validateattributes(cfgLLData, {'bleLLDataChannelPDUConfig'}, {'scalar'}, ...
    'bleLLDataChannelPDU', 'cfgLLData', 1);

% Initialize
octetLen = 8;
payloadLength = 0;

% The payload field shall be in the range of 0 to 251 octets in length. MIC
% (Message Integrity Check, 4-octets) is currently not supported. Refer
% Bluetooth Core Specification v5.0 | Vol 6, Part B, Section 2.4.
maxPDULength = 251;

% Upper-layer payload is not passed
if nargin == 1 || isempty(llPayload)
    % For code generation
    payloadBits = zeros(1, 0);
    
    % Upper-layer payload is passed as an argument
else
    % Validate input argument LLPAYLOAD
    llPayload = ble.internal.convertPayloadToHexArray(llPayload);
    % Calculate payload length
    payloadLength = numel(llPayload)/2;
    % Convert hexadecimal bytes to bits
    payloadBits = reshape(int2bit(...
        hex2dec(llPayload), octetLen, 0), [], 1);
end

% Link Layer control PDU
if (strcmpi(cfgLLData.LLID, 'Control'))
    controlConfig = cfgLLData.ControlConfig;
    [payload, payloadLength] = generateLLControlPDUPayload(controlConfig);
    
% Link Layer data PDU
else
    coder.internal.errorIf((payloadLength > maxPDULength), ...
        'bluetooth:bleLL:InvalidLLDataPDULength');
    % Empty payload passed for PDU with LLID 'Data (start
    % fragment/complete)'
    coder.internal.errorIf((payloadLength == 0 && ...
        strcmpi(cfgLLData.LLID, 'Data (start fragment/complete)')), ...
        'bluetooth:bleLL:LLDataPayloadNotSupplied');
    payload = payloadBits;
end

% Header
header = generateHeader(cfgLLData, payloadLength);

% Put it all together (Data channel PDU)
dataPDU = [header; payload];

% Append CRC to the generated data channel PDU
pdu = ble.internal.appendCRC(dataPDU, cfgLLData.CRCInitialization);
end

% Generate header
function header = generateHeader(cfg, payloadLength)
% Initialize
octetLen = 8;

% LLID (2-bits)
llid = generateLLID(cfg.LLID);

% NESN (Next Expected Sequence Number) (1-bit)
nesn = double(cfg.NESN);

% SN (Sequence Number) (1-bit)
sn = double(cfg.SequenceNumber);

% MD (More Data) (1-bit)
md = double(cfg.MoreData);

% RFU (Reserved for future use) (3-bits)
rfu = int2bit(0, 3, 0);

% Length (8-bits)
lengthValue = int2bit(payloadLength, octetLen, 0);

% Put it all together
header = [llid; nesn; sn; md; rfu; lengthValue];
end

% Generate control PDU payload
function [payload, payloadLength] = generateLLControlPDUPayload(cfg)
% Initialize
octetLen = 8;
doubleOctetLen = 2*octetLen;
controlData = zeros(1, 0);

% Opcode (1 - octet)
opcode = generateOpcode(cfg.Opcode);

switch (cfg.Opcode)
    case 'Connection update indication'
        % Validate interdependent properties
        % Validate Peripheral latency
        success = validatePeripheralLatency(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLPeripheralLatency', ...
            cfg.PeripheralLatency, cfg.Opcode);
        % Validate connection timeout
        success = validateConnectionTimeout(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLConnectionTimeout', ...
            cfg.ConnectionTimeout, cfg.Opcode);
        % Validate window size
        success = validateWindowSize(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLWindowSize', ...
            cfg.WindowSize, cfg.Opcode);
        % Validate window offset
        success = validateWindowOffset(cfg);
        coder.internal.errorIf(~success, 'bluetooth:bleLL:InvalidLLWindowOffset', ...
            cfg.WindowOffset, cfg.Opcode);
        
        % Transmit Window Size (1-octet)
        windowSize = int2bit(...
            cfg.WindowSize, octetLen, 0);
        
        % Transmit Window Offset (2-octets)
        windowOffset = int2bit(...
            cfg.WindowOffset, doubleOctetLen, 0);
        
        % Connection Interval (2-octets)
        interval = int2bit(...
            cfg.ConnectionInterval, doubleOctetLen, 0);
        
        % Peripheral Latency (2-octets)
        latency = int2bit(...
            cfg.PeripheralLatency, doubleOctetLen, 0);
        
        % Connection Supervision Timeout (2-octets)
        timeout = int2bit(...
            cfg.ConnectionTimeout, doubleOctetLen, 0);
        
        % Instant(2-octets)
        instant = int2bit(...
            cfg.Instant, doubleOctetLen, 0);
        
        % Put it all together to form control data (11-octets)
        controlData = [windowSize; windowOffset; interval; latency; ...
            timeout; instant];
        
    case 'Channel map indication'
        % Channel Map (5-octets)
        channelMap = generateChannelMap(cfg.UsedChannels);
        
        % Instant (2-octets)
        instant = int2bit(...
            cfg.Instant, doubleOctetLen, 0);
        
        % Put it all together to form control data (7-octets)
        controlData = [channelMap; instant];

    case {'Terminate indication', 'Reject indication'}
        % Error Code (1-octet)
        errorCode = generateErrorCode(cfg.ErrorCode);
        
        % Form control data (1-octet)
        controlData = errorCode;

    case 'Unknown response'
        % Unknown type (1-octet)
        unknownOpcode = int2bit(...
            hex2dec(cfg.UnknownOpcode), octetLen, 0);
        
        % Form control data (1-octet)
        controlData = unknownOpcode;
        
    case 'Version indication'
        % Bluetooth LE Link Layer version number (1-octet)
        versionNum = generateVersionCode(cfg.VersionNumber);
        
        % Manufacturer company identifier (2-octets)
        companyID = int2bit(...
            hex2dec(cfg.CompanyIdentifier), doubleOctetLen, 0);
        
        % Sub-version number (2-octets)
        subVersionNum = int2bit(...
            hex2dec(cfg.SubVersionNumber), doubleOctetLen, 0);
        
        % Put it all together to form control data (5-octet)
        controlData = [versionNum; companyID; subVersionNum];
end
% Prepare payload for control PDU
payload = [opcode; controlData];
% Calculate control PDU payload length
payloadLength = (numel(payload))/octetLen;
end

% Generate LLID from the given LLID description string
function llid = generateLLID(llidType)
% Refer "Table 2.11" Bluetooth Core Specification v5.0 | Vol 6, Part B

% Initialize
llid = 0;

switch llidType
    case 'Data (continuation fragment/empty)'
        llid = int2bit(1, 2, 0);
    case 'Data (start fragment/complete)'
        llid = int2bit(2, 2, 0);
    case 'Control'
        llid = int2bit(3, 2, 0);
end
end

% Generate opcode from the given opcode description string
function opcode = generateOpcode(opCodeType)
% Refer "Table 2.18" in Bluetooth Core Specification v5.0 | Vol 6, Part B

% Initialize
octetLen = 8;
opcodeValue = 0;

switch opCodeType
    case 'Connection update indication'
        opcodeValue = 0;
    case 'Channel map indication'
        opcodeValue = 1;
    case 'Terminate indication'
        opcodeValue = 2;
    case 'Unknown response'
        opcodeValue = 7;
    case 'Version indication'
        opcodeValue = 12;
    case 'Reject indication'
        opcodeValue = 13;
end
opcode = int2bit(opcodeValue, octetLen, 0);
end

% Generate error code from the given error code description string
function errorCode = generateErrorCode(errorString)
% Refer "Table 1.1" in Bluetooth Core Specification v5.0 | Vol 2, Part D

% Initialize
octetLen = 8;
errorValue = 0;

switch errorString
    case 'Success'
        errorValue = 0;
    case 'Unknown connection identifier'
        errorValue = 2;
    case 'Hardware failure'
        errorValue = 3;
    case 'Memory capacity exceeded'
        errorValue = 7;
    case 'Connection timeout'
        errorValue = 8;
    case 'Connection limit exceeded'
        errorValue = 9;
    case 'Connection already exists'
        errorValue = 11;
    case 'Command disallowed'
        errorValue = 12;
    case 'Connection rejected due to limited resources'
        errorValue = 13;
    case 'Connection rejected due to unacceptable BD_ADDR'
        errorValue = 15;
    case 'Connection accept timeout exceeded'
        errorValue = 16;
    case 'Unspecified error'
        errorValue = 30;
    case 'Invalid LL parameters'
        errorValue = 31;
    case 'Unsupported LL parameter value'
        errorValue = 32;
    case 'Role change not allowed'
        errorValue = 33;
    case 'LL response timeout'
        errorValue = 34;
    case 'LL procedure collision'
        errorValue = 35;
    case 'Instant passed'
        errorValue = 40;
    case 'Channel classification not supported'
        errorValue = 46;
    case 'Extended inquiry response too large'
        errorValue = 54;
    case 'Advertising timeout'
        errorValue = 57;
    case 'Connection rejected due to no suitable channel found'
        errorValue = 58;
    case 'Controller busy'
        errorValue = 59;
    case 'Unacceptable connection parameters'
        errorValue = 60;
    case 'Connection failed to be established'
        errorValue = 62;
    case 'Unknown advertising identifier'
        errorValue = 66;
    case 'Limit reached'
        errorValue = 67;
    case 'Operation cancelled by host'
        errorValue = 68;
end
errorCode = int2bit(errorValue, octetLen, 0);
end

% Generate version code from given version description string
function versionCode = generateVersionCode(version)
% Refer Link Layer assigned numbers from Bluetooth SIG

% Initialize
octetLen = 8;
versionCode = 0;

switch version
    case '4.0'
        versionCode = 6;
    case '4.1'
        versionCode = 7;
    case '4.2'
        versionCode = 8;
    case '5.0'
        versionCode = 9;
end
versionCode = int2bit(versionCode, octetLen, 0);
end

% Generate bitmap indicating good (1) & bad (0) channels from used channels
function bitmap = generateChannelMap(chanList)
% Initialize
octetLen = 8;
fiveOctetLen = 5*octetLen;
bitmap = zeros(fiveOctetLen, 1);
% Set the indices specified by the channel list to 1
bitmap(chanList+1) = 1;
end
