classdef (Abstract) hNRNodeBase < handle
%hNRNodeBase Define a common protocol stack interface for NR gNB and UE node

% Copyright 2019 The MathWorks, Inc.

    properties (Access = public)
        % RLCEntitiesList A set of RLC entities, where each entity
        % corresponds to a configured logical channel
        RLCEntitiesList = {};

        % MACEntity A MAC entity associated with either a UE or a gNB
        MACEntity
    end

    properties (Access = protected)
        % RLCEntityTypesList Type of RLC entities Tx/Rx/both(Tx and Rx)
        RLCEntityTypesList = {};

        % CumulRLCStats Contains the cumulative RLC statistics
        CumulRLCStats

        % SymbolCount Keeps track of count of symbol finished in current
        % period of 1 ms. As soon as symbols equivalent to duration of 1 ms
        % are finished, a trigger is sent to RLC. One trigger is sent every
        % millisecond.
        SymbolCount
    end

    properties (Access = protected, Constant)
        % NumLogicalChannels Maximum number of logical channels that can be configured
        NumLogicalChannels = 32;
        % NumRLCStats Number of RLC layer statistics collected
        NumRLCStats = 17;
        % LcInfoIndex Indices of RNTI and LCID in the RLC statistics
        LcInfoIndex = (1:2);
        % TxStatsIndex Indices of the RLC Tx statistics
        TxStatsIndex = (3:8);
        % RxStatsIndex Indices of the RLC Rx statistics
        RxStatsIndex = (9:17);
    end

    methods (Access = public)
        function configureLogicalChannel(obj, rnti, rlcBearerConfig)
            %configureLogicalChannel Configure a logical channel
            %
            % configureLogicalChannel(OBJ, RNTI, RLCBEARERCONFIG)
            % configures a logical channel by creating an associated RLC
            % entity and updating this logical channel information in the
            % MAC.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.
            %
            % RLCBEARERCONFIG is a RLC bearer configuration structure.
            % For RLC transmitter entity RLCBEARERCONFIG contains
            % following fields:
            %       EntityDir - Indicates where to establish a logical
            %       channel. EntityDir = 0, represents DL logical channel
            %       EntityDir = 1, represents UL logical channel
            %       EntityDir = 2, represents UL+DL logical channel
            %       LCID - Logical channel id
            %       SnFieldLength  - Sequence number field length (in bits)
            %       MaxTxBufferLength  - Maximum buffer length of RLC transmitter entity
            %       Priority - Priority of the logical channel
            %       PBR - Prioritized bit rate (in kilo bytes per second)
            %       BucketSizeDuration - Bucket size duration (in ms)
            %       LCGID - Logical channel group id
            %   For RLC receiver entity RLCBEARERCONFIG contains the
            %   following fields.
            %       EntityDir - Indicates where to establish a logical
            %       channel. EntityDir = 0, represents DL logical channel
            %       EntityDir = 1, represents UL logical channel
            %       EntityDir = 2, represents UL+DL logical channel
            %       LCID - Logical channel id
            %       SnFieldLength - Sequence number field length (in bits)
            %       ReassemblyTime  - Reassembly timer value (in ms)

            % Determine the index of the logical channel set index
            if obj.MACEntity.MACType
                logicalChannelSetIndex = 1; % If it is UE
            else
                % If it is gNB then RNTI becomes logical channel set index
                logicalChannelSetIndex = rnti;
            end

            % Create RLC entity for logical channels and set their type
            if rlcBearerConfig.EntityDir == 0
                if obj.MACEntity.MACType % UE MAC
                    obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMReceiver(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.ReassemblyTime);
                    % Set RLC type in the RLC entity types list
                    obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMRx';
                else % gNB MAC
                    % Create the RLC UM transmitter
                    obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMTransmitter(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.MaxTxBufferLength);
                    % Set RLC type in the RLC entity types list
                    obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMTx';
                end
                % Add the logical channel information in the MAC layer
                obj.MACEntity.addLogicalChannelInfo(rlcBearerConfig, rnti);
            elseif rlcBearerConfig.EntityDir == 1
                if obj.MACEntity.MACType % UE MAC
                    % Create the RLC UM receiver
                    obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMTransmitter(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.MaxTxBufferLength);
                    % Set RLC type in the RLC entity types list
                    obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMTx';
                else
                    % Create the RLC UM receiver
                    obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMReceiver(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.ReassemblyTime);
                    % Set RLC type in the RLC entity types list
                    obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMRx';
                end
                % Add the logical channel information in the MAC layer
                addLogicalChannelInfo(obj.MACEntity, rlcBearerConfig, rnti);
            elseif rlcBearerConfig.EntityDir == 2
                % Create the RLC UM transmitter
                obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMTransmitter(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.MaxTxBufferLength);
                % Create the RLC UM receiver
                obj.RLCEntitiesList{logicalChannelSetIndex, 1}{end+1, 1} = hNRUMReceiver(rnti, rlcBearerConfig.LCID, rlcBearerConfig.SnFieldLength, rlcBearerConfig.ReassemblyTime);
                % Add the logical channel information in the MAC layer
                addLogicalChannelInfo(obj.MACEntity, rlcBearerConfig, rnti);
                % Set RLC type in the RLC entity types list
                obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMTx';
                % Set RLC type in the RLC entity types list
                obj.RLCEntityTypesList{logicalChannelSetIndex, 1}{end+1, 1} = 'UMRx';
            else
                error('RLC entity type must be 0, 1, or 2.');
            end
        end

        function receiveRLCSDU(obj, rnti, lcid, rlcSDU)
            %receiveRLCSDU Receive the RLC SDU from the higher layers
            %
            % receiveRLCSDU(OBJ, RNTI, LCID, RLCSDU) forwards the
            % received RLC SDU to the respective RLC entity and sends
            % the updated buffer status information to the MAC.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.
            %
            % LCID is a logical channel id, specified in the range
            % between 1 and 32.
            %
            % RLCSDU is a column vector of octets in decimal format.

            % Get the corresponding RLC entity
            rlcEntity = getRLCEntity(obj, rnti, lcid, 'UMTx');

            % Send the received RLC SDU to the corresponding RLC entity
            bufferStatusReport = rlcEntity.receiveRLCSDU(rlcSDU);
            % Inform the updated buffer status information to the MAC
            updateBufferStatus(obj.MACEntity, bufferStatusReport);
        end

        function rlcPDUs = sendRLCPDUs(obj, rnti, lcid, grantSize, remainingGrant)
            %sendRLCPDUs Callback to RLC for getting RLC PDUs of a logical channel (for transmission)
            %
            % MACSDUS = sendRLCPDUs(OBJ, RNTI, LCID, GRANTSIZE,
            % REMAININGGRANT) returns the RLC PDUs.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.
            %
            % LCID is a logical channel id, specified in the range
            % between 1 and 32.
            %
            % GRANTSIZE is a scalar integer that specifies the resource
            % grant size in bytes.
            %
            % REMAININGGRANT is a scalar integer that specifies the remaining
            % resource grant (in bytes) available for the current Tx.
            %
            % RLCPDUS is a cell array of RLC PDUs. Each element in the
            % cell represents an RLC PDU which contains column vector of
            % octets in decimal format.

            % Get the corresponding RLC entity
            rlcEntity = getRLCEntity(obj, rnti, lcid, 'UMTx');

            [rlcPDUs, bufferStatusReport] = rlcEntity.notifyTxOpportunity(grantSize, remainingGrant);
            % Inform the updated buffer status information to the MAC
            updateBufferStatus(obj.MACEntity, bufferStatusReport);
        end

        function receiveRLCPDUs(obj, rnti, lcid, rlcPDU)
            %receiveRLCPDUs Callback to RLC to receive an RLC PDU for a logical channel
            %
            % receiveRLCPDUs(OBJ, RNTI, LCID, RLCPDU)
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1 in
            % 3GPP TS 38.321. It identifies the transmitter UE.
            %
            % LCID is a logical channel id, specified in the range between
            % 1 and 32.
            %
            % RLCPDU RLC PDU extracted from received MAC PDU, to be sent to
            % RLC.

            % Get the corresponding RLC entity
            rlcEntity = getRLCEntity(obj, rnti, lcid, 'UMRx');

            receivePDU(rlcEntity, rlcPDU);
        end

        function cqiRBs = getChannelQualityStatus(obj, linkDir, rnti)
            %getChannelQualityStatus Return the channel quality information of a UE based on link direction
            %
            % CQIRBS = getChannelQualityStatus(OBJ, LINKDIR, RNTI)
            % returns the channel quality information for the specified
            % LINKDIR for the UE.
            %
            % CQIRBS it is an array of integers that specifies the CQI
            % values over the RBs of channel bandwidth
            %
            % LINKDIR is a scalar integer, specified as 0 or 1. LINKDIR = 0
            % represents downlink and LINKDIR = 1 represents uplink.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.

            if obj.MACEntity.MACType
                % If called by UE, return its DL channel quality
                cqiRBs = getChannelQualityStatus(obj.MACEntity);
            else
                % If called by gNB, return the linkDir (DL/UL) channel quality for the UE
                cqiRBs = getChannelQualityStatus(obj.MACEntity, linkDir, rnti);
            end
        end

        function updateChannelQualityStatus(obj, cqiRBs, varargin)
            %updateChannelQualityStatus Update the channel quality information at the gNB and UEs
            %
            % updateChannelQualityStatus(OBJ, CQIRBS, LINKDIR, RNTI)
            % helps in updating the channel quality information based on
            % LINKDIR and RNTI at gNB.
            %
            % updateChannelQualityStatus(OBJ, CQIRBS) updates the
            % downlink channel quality information at UE.
            %
            % CQIRBS is an array of integers that specifies the CQI
            % values over the RBs of channel bandwidth.
            %
            % LINKDIR is a scalar integer, specified as 0 or 1. LINKDIR = 0
            % represents downlink and LINKDIR = 1 represents uplink.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.

            if numel(varargin) == 2
                updateChannelQualityStatus(obj.MACEntity, cqiRBs, varargin{1}, varargin{2});
            else
                updateChannelQualityStatus(obj.MACEntity, cqiRBs);
            end
        end

        function symbolType = currentSymbolType(obj)
            %currentSymbolType Get current running symbol type: DL/UL/Guard

            symbolType = currentSymbolType(obj.MACEntity);
        end

        function rlcStats = getRLCStatistics(obj, rnti)
            %getRLCStatistics Return the RLC statistics of the current slot
            %
            % RLCSTATS = getRLCStatistics(OBJ) is called by gNB to return
            % statistics of RLC entities of the UEs.
            %
            % RLCSTATS = getRLCStatistics(OBJ, RNTI) is called by UE to
            % return statistics of its RLC entities.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.
            %
            % RLCSTATS - RLC statistics represented as a N-by-P matrix,
            % where 'N' represent the number of logical channels and 'P'
            % represent the number of RLC layer statistics collected. The
            % 'P' columns are as follows 'RNTI', 'LCID', 'TxPDCPPDU',
            % 'TxPDCPPDUBytes', 'TxRLCSDUDiscarded',
            % 'TxRLCSDUDiscardedBytes' 'TxRLCPDU', 'TxRLCPDUBytes',
            % 'RxPDCPPDU', 'RxPDCPPDUBytes', 'RxRLCPDU', 'RxRLCPDUBytes',
            % 'RxRLCPDUDuplicate', 'RxRLCPDUDuplicateBytes',
            % 'RxRLCPDUDropped', 'RxRLCPDUDroppedBytes',
            % 'TimerReassemblyTimedOut'.

            % Row index in the RLC Entity list
            if obj.MACEntity.MACType % UE
                numLogicalChannels = size(obj.CumulRLCStats, 1);
                rowIdx = 1;
            else % gNB
                % If it is gNB then RNTI becomes row index
                rowIdx = rnti;
                numLogicalChannels = size(obj.CumulRLCStats, 2);
            end

            rlcStatsList = zeros(numLogicalChannels, obj.NumRLCStats);
            activeLCHIds = zeros(numLogicalChannels, 1);
            for i = 1:length(obj.RLCEntitiesList{rowIdx, 1})
                % Get the cumulative RLC statistics of all logical
                % channels of a UE
                rlcEntity = obj.RLCEntitiesList{rowIdx, 1}{i, 1};
                receivedStats = rlcEntity.getStatistics()';
                stats(1:2) = receivedStats(1:2);
                lcid = stats(2);
                activeLCHIds(lcid) = lcid;
                if strcmp(obj.RLCEntityTypesList{rowIdx, 1}{i, 1}, 'UMTx')
                    % RLC Tx statistics
                    indicesToUpdate = obj.TxStatsIndex;
                else
                    % RLC Rx statistics
                    indicesToUpdate = obj.RxStatsIndex;
                end
                rlcStatsList(lcid, obj.LcInfoIndex) = stats(obj.LcInfoIndex);

                % Calculate the RLC statistics of all logical channels
                % of a UE for the current slot
                if obj.MACEntity.MACType % RLC statistics at UE
                    rlcStatsList(lcid, indicesToUpdate) = receivedStats(3:end) - obj.CumulRLCStats(lcid, indicesToUpdate);
                    obj.CumulRLCStats(lcid, indicesToUpdate) = receivedStats(3:end);
                    obj.CumulRLCStats(lcid, obj.LcInfoIndex) = receivedStats(obj.LcInfoIndex);
                else % RLC statistics at gNB for UE
                    rlcStatsList(lcid, indicesToUpdate) = receivedStats(3:end) - (squeeze(obj.CumulRLCStats(rowIdx, lcid, indicesToUpdate)))';
                    obj.CumulRLCStats(rowIdx, lcid, indicesToUpdate) = receivedStats(3:end);
                    obj.CumulRLCStats(rowIdx, lcid, obj.LcInfoIndex) = receivedStats(obj.LcInfoIndex);
                end
            end
            rlcStats = rlcStatsList(nonzeros(activeLCHIds), :); % Send the information of active logical channels
        end

        function controlTx(obj, varargin)
            %controlTx Send the control information i.e UL and DL assignments
            %
            % controlTx(OBJ) UE sends the control information to gNB (BSR and PDSCH feedback)
            %
            % controlTx(OBJ, RESOURCEASSIGNMENTSUL, RESOURCEASSIGNMENTSDL)
            % gNB sends the control information to UEs (UL and DL assignments)
            %
            % RESOURCEASSIGNMENTSUL and RESOURCEASSIGNMENTSDL represent the
            % UL and DL resources assignments to UEs, respectively

            if obj.MACEntity.MACType % UE
                controlTx(obj.MACEntity);
            else % gNB
                controlTx(obj.MACEntity, varargin{1}, varargin{2});
            end
        end

        function dataTx(obj)
            % dataTx Send the MAC PDU (DL or UL) as per the properties of grant
            %
            % dataTx(OBJ) sends the MAC PDU as per the properties of DL or
            % UL grants, if the current symbol is Tx start time.

            dataTx(obj.MACEntity);
        end

        function [throughputServing, goodputServing] = getCurrTTIBytes(obj)
            %getCurrTTIBytes Return amount of throughput and goodput bytes sent in current symbol
            %
            % [THROUGHPUTPUTSERVING GOODPUTPUTSERVING] =
            % getCurrTTIBytes(OBJ) returns the amount of throughput and
            % goodput bytes sent in the TTI which starts at current
            % symbol
            %
            % THROUGHPUTSERVING - MAC transmission (throughput) in bytes
            %
            % GOODPUTSERVING - Only new MAC transmission (goodput) in bytes

            [throughputServing, goodputServing] = getCurrTTIBytes(obj.MACEntity);
        end

        function advanceTimer(obj, tickGranularity)
            %advanceTimer Advance the timer by tick granularity
            %
            % advanceTimer(OBJ, TICKGRANULARITY) Advance the timer by tick
            % granularity. Additionally, send periodic 1 ms trigger to RLC
            %
            % TICKGRANULARITY - Specified in terms of number of symbols. It
            % is 1 for symbol-based scheduling, so execution happens
            % symbol-by-symbol. It is 14 for slot based scheduling, so
            % execution jumps from slot boundary to next slot boundary

            advanceTimer(obj.MACEntity, tickGranularity);
            % Trigger RLC timer for every 1 ms
            obj.SymbolCount = obj.SymbolCount + tickGranularity;

            scs = obj.MACEntity.SCS;
            if obj.SymbolCount == (14 * scs/15)
                rlcMsTimer(obj);
                % Reset after every 1 ms
                obj.SymbolCount = 0;
            end
        end

        function controlRx(obj)
            %controlRx Receive the control information
            %
            % UEs call it to receive DL and UL assignments. gNB calls it to
            % receive BSR and PDSCH packet feedback.

            controlRx(obj.MACEntity);
        end

        function rxResult = dataRx(obj)
            %dataRx Receive the packets, and return the information of Rx success/failure
            %
            % RXRESULT = dataRx(OBJ) Returns the information of Rx
            % success/failure of the packets that are scheduled for current
            % symbol.
            %
            % RXRESULT - If called for UE, it is the status of the
            % packet received from the gNB in the current symbol. If
            % called for gNB, it is a cell array of size equal to number of
            % UEs and contains information about status of packets that are
            % received in the current symbol from the UEs.
            % Each element of RXRESULT contains one of the following
            % values.
            %       - 'RxSuccess' represents successful reception
            %       - 'RxSuccess' represents failed reception
            %       - 'noRx' represents no reception was scheduled

            rxResult = dataRx(obj.MACEntity);
        end

        function bufferStatus = getBufferStatus(obj)
            %getBufferStatus Return the current buffer status of UEs
            %
            % BUFFERSTATUS = getBufferStatus(OBJ) Returns the UL buffer
            % status of UE, when called by UE. Returns DL buffer
            % status array containing buffer amount for each UE, when
            % called by gNB.
            %
            % BUFFERSTATUS - Represents the buffer size in bytes.

            bufferStatus = getUEBufferStatus(obj.MACEntity);
        end
    end

    methods(Access = private)
        function rlcMsTimer(obj)
            %rlcMsTimer For each 1 ms send a trigger to all the RLC UM receivers

            for ueRLCEntityIdx = 1:length(obj.RLCEntitiesList)
                for rlcEntityIdx = 1:length(obj.RLCEntitiesList{ueRLCEntityIdx, 1})
                    if strcmp(obj.RLCEntityTypesList{ueRLCEntityIdx, 1}{rlcEntityIdx, 1}, 'UMRx')
                        rlcEntity = obj.RLCEntitiesList{ueRLCEntityIdx, 1}{rlcEntityIdx, 1};
                        timerUpdateTrigger(rlcEntity);
                    end
                end
            end
        end

        function rlcEntity = getRLCEntity(obj, rnti, lcid, type)
            %getRLCEntity Return the RLC entity
            %
            % RLCENTITY = getRLCEntityIndex(OBJ, RNTI, LCID, TYPE) Returns the
            % RLCENTITY reference based on RNTI and LCID and TYPE.
            %
            % RNTI is a radio network temporary identifier, specified in
            % the range between 1 and 65519 inclusive. Refer table 7.1-1
            % in 3GPP TS 38.321.
            %
            % LCID is a logical channel id, specified in the range
            % between 1 and 32.
            %
            % TYPE - Type of RLC entity

            % Row index in the RLC Entity list
            if obj.MACEntity.MACType % UE
                rowIdx = 1;
            else % gNB
                % If it is gNB then RNTI becomes row index
                rowIdx = rnti;
            end
            rlcEntity = [];
            for colIdx = 1:length(obj.RLCEntitiesList{rowIdx, 1})
                if (obj.RLCEntitiesList{rowIdx, 1}{colIdx, 1}.LogicalChannelID == lcid) && ...
                        (strcmp(obj.RLCEntityTypesList{rowIdx, 1}{colIdx, 1}, type))
                    rlcEntity = obj.RLCEntitiesList{rowIdx, 1}{colIdx, 1};
                    break;
                end
            end
            if isempty(rlcEntity)
                error(['There is no logical channel with id ', num2str(lcid), ' and type ', type, ' associated with the UE RNTI ', num2str(rnti)]);
            end
        end
    end
end