classdef macQueueManagement < handle
%macQueueManagement Create a WLAN MAC queue management object
%
%   OBJ = macQueueManagement(NUMSTATIONS, MAXQUEUELENGTH) creates a WLAN
%   MAC queue management object, OBJ, for number of stations NUMSTATIONS
%   with a maximum queue length of MAXQUEUELENGTH.
%
%   macQueueManagement properties:
%   NumStations         - Number of stations in the network
%   MaxQueueLength      - Maximum size of a queue
%   TxQueueLengths      - Number of MSDUs buffered for transmission
%   RetryFlags          - Retransmission flags for dequeued frames
%   RetryQueueLengths   - Number of MSDUs in retransmission queue
%   MSDULengths         - Length of each MSDU buffered for transmission

%   Copyright 2019 The MathWorks, Inc.

%#codegen

properties(SetAccess = private, GetAccess = public)
    %NumStations Number of stations in the network
    % NumStations is a scalar representing number of stations in the
    % network.
    NumStations (1, 1) {mustBeNumeric}

    %MaxQueueLength Maximum size of a queue
    % MaxQueueLength is a scalar representing maximum number of MSDUs that
    % can be stored in a queue.
    MaxQueueLength (1, 1) {mustBeNumeric}

    %TxQueueLengths Number of MSDUs buffered for transmission
    % TxQueueLengths is an array of size M x N where M is the number of
    % stations in the network and N is the maximum number of ACs. Each
    % element represents number of MSDU's present in transmission queue in
    % a station in corresponding AC.
    TxQueueLengths

    %RetryFlags Retransmission flags for dequeued frames
    % RetryFlags is an array of size M x N where M is the number of
    % stations in the network and N is the maximum number of ACs. Each
    % element is of type boolean where true indicates that MSDU's are
    % dequeued from retry queue and false indicates that MSDU's are
    % dequeued from transmission queue.
    RetryFlags

    %RetryQueueLengths Number of MSDUs in retransmission queue
    % RetryQueueLengths is an array of size M x N where M is the number of
    % stations in network and N is the maximum number of ACs. Each element
    % represents number of MSDU's in retry queue of corresponding station
    % and AC that are not discarded.
    RetryQueueLengths

    %MSDULengths Length of each MSDU buffered for transmission
    % MSDULengths is an array of size M x N x O where M is the number of
    % stations in network, N is the maximum number of ACs and O is the
    % maximum queue length. Each element represents length of MSDU present
    % in the queue of corresponding station and corresponding AC.
    MSDULengths
end

properties(Constant, Hidden)
   % Maximum number of users in a multi-user(MU) transmission using OFDMA.
   % In 20MHz OFDMA transmission max possible users are 9.
   MaxMUStations = 9;

   % Maximum number of access categories (AC). IEEE 802.11 quality of
   % service (QoS) defines application data priorities by grouping them
   % into 4 ACs.
   MaxACs =4;

   % Maximum number of subframes in a single A-MPDU
   MaxSubframesCount = 64;
end

properties(Access = private)
    %Packet Application packet
    % Packet is a scalar structure containing the MSDU information
    Packet

    %TxQueues Transmission queues per station per AC
    % TxQueues is an array of size M x N x O where M is the number of
    % stations in network, N is the maximum number of ACs and O is the
    % maximum queue length. Each element represents an MSDU structure.
    TxQueues

    %ReadIndices Read indices of each queue
    % ReadIndices is an array of size M x N where M is number of stations
    % in the network and N is the maximum number of ACs.
    ReadIndices

    %WriteIndices Write indices of each queue
    % WriteIndices is an array of size M x N where M is number of
    % stations in the network and N is the maximum number of ACs.
    WriteIndices

    %PacketToAggregate MSDUs information
    % PacketToAggregate is a scalar structure containing information of the
    % MSDUs to generate PSDU
    PacketToAggregate

    %RetryQueues Retransmission queues per station per AC
    % RetryQueues is an array of size M x N where M is number of stations
    % in the network and N is the maximum number of ACs. Each element
    % represents a frame of corresponding AC dequeued from a station.
    RetryQueues

    %RetryMSDUIndices MSDU indices that are waiting for retransmission
    % RetryMSDUIndices is an array of size M x N x O where M is the number
    % of stations in network, N is the maximum number of ACs and O is the
    % maximum queue length. Non- zero elements represents the indices that
    % are not discarded from corresponding AC of a station.
    RetryMSDUIndices

    %FrameList dequeued MSDUs corresponding to given stations
    % FrameList is a vector of structures with N elements. Where N is the
    % maximum number of users in an MU transmission. Each structure
    % contains MSDUs information to generate PSDU.
    FrameList
end

methods(Access = private)
    function numDequeued = dequeueRetryQ(obj, station, ac, numMSDU, staIdx)
    % Dequeues required number of MSDUs from retry queue and returns number
    % of MSDUs dequeued.

        % Find the indices that are not discarded.
        retryIndices = find(obj.RetryMSDUIndices(station, ac, :));

        % Retry queue contains less number of MSDUs than to be dequeued
        if obj.RetryQueueLengths(station, ac) < numMSDU
            numMSDU = obj.RetryQueueLengths(station, ac);
        end

        for idx = 1:numMSDU
            obj.FrameList(staIdx).timestamp(idx) = obj.RetryQueues(station, ac).timestamp(retryIndices(idx));
            obj.FrameList(staIdx).msduLength(idx) = obj.RetryQueues(station, ac).msduLength(retryIndices(idx));
            obj.FrameList(staIdx).data(idx, :) = obj.RetryQueues(station, ac).data(retryIndices(idx), :);
        end
        % Set retry flag to true to indicate dequeue from retry queue
        obj.RetryFlags(station, ac) = true;
        numDequeued = numMSDU;
    end

    function dequeuedMSDUInfo = dequeueTxQ(obj, station, ac, numMSDU, staIdx, MSDUIdx)
    % Dequeues required number of MSDUs from transmission queue and inserts
    % them into FrameList at index corresponding to the staIdx and  MSDUIdx.

        dequeuedMSDUInfo = repmat(obj.Packet, macQueueManagement.MaxSubframesCount, 1);
        for idx = MSDUIdx:MSDUIdx + numMSDU - 1

            readIndex = obj.ReadIndices(station, ac);
            obj.FrameList(staIdx).timestamp(idx) = obj.TxQueues(station, ac, readIndex).timestamp;
            obj.FrameList(staIdx).msduLength(idx) = obj.TxQueues(station, ac, readIndex).msduLength;
            obj.FrameList(staIdx).data(idx, :) = obj.TxQueues(station, ac, readIndex).data;
            obj.ReadIndices(station, ac) = obj.ReadIndices(station, ac) + 1;

            % Return dequeued MSDUs info
            dequeuedMSDUInfo(idx - MSDUIdx + 1).timestamp = obj.TxQueues(station, ac, readIndex).timestamp;
            dequeuedMSDUInfo(idx - MSDUIdx + 1).msduLength = obj.TxQueues(station, ac, readIndex).msduLength;
            dequeuedMSDUInfo(idx - MSDUIdx + 1).data = obj.TxQueues(station, ac, readIndex).data;

            % Read index must be reset to 1 after reaching
            % maximum queue length.
            if obj.ReadIndices(station, ac) > obj.MaxQueueLength
                obj.ReadIndices(station, ac) = 1;
            end
        end
     end

    function enqueueRetryQ(obj, station, ac, frame)
    % Inserts frame containing 'numMSDU' number of packets from
    % transmission queue into retry queue and increments retry queue length

        obj.RetryQueues(station, ac) = frame;
        obj.RetryQueueLengths(station, ac) = obj.RetryQueueLengths(station, ac) + nnz(frame.timestamp);
    end

    function fillCommonMSDUInfo(obj, station, ac, staIdx, retryFlag)
    % Fills common MSDU information like dstMacAddr, ac and dstId in
    % FrameList at index set to staIdx.

        switch retryFlag
            case 1
                packetToAggregate = obj.RetryQueues(station, ac);
                obj.FrameList(staIdx).dstMacAddr = packetToAggregate.dstMacAddr;
                obj.FrameList(staIdx).ac = packetToAggregate.ac;
                obj.FrameList(staIdx).dstId = packetToAggregate.dstId;
            case 0
                readIdx = obj.ReadIndices(station, ac);
                packet = obj.TxQueues(station, ac, readIdx);
                obj.FrameList(staIdx).dstMacAddr = packet.dstMacAddr;
                obj.FrameList(staIdx).ac = packet.ac;
                obj.FrameList(staIdx).dstId = packet.dstId;
        end
    end
end
methods

    function obj = macQueueManagement(NumStations, MaxQueueLength)
    % Constructor to create a queue object for stations in the network

        obj.NumStations = NumStations;
        obj.MaxQueueLength = MaxQueueLength;

        % Initialize transmission queue information
        obj.Packet = struct('timestamp', 0, 'dstMacAddr', zeros(1,6), 'msduLength', 0,...
                        'ac', 0, 'dstId', 0, 'data', zeros(2304, 1, 'uint8'));
        obj.TxQueues = repmat(obj.Packet, NumStations, macQueueManagement.MaxACs, MaxQueueLength);
        obj.TxQueueLengths = zeros(NumStations, macQueueManagement.MaxACs);
        obj.ReadIndices = ones(NumStations, macQueueManagement.MaxACs);
        obj.WriteIndices = ones(NumStations, macQueueManagement.MaxACs);
        obj.MSDULengths = zeros(NumStations, macQueueManagement.MaxACs, MaxQueueLength);

        % Initialize retry queue information
        obj.PacketToAggregate = struct('msduCount', 0, ...
                                        'timestamp', zeros(macQueueManagement.MaxSubframesCount, 1),...
                                        'dstMacAddr', zeros(1,6),...
                                        'msduLength', zeros(macQueueManagement.MaxSubframesCount, 1),...
                                        'ac', 0, 'dstId', 0,...
                                        'data', zeros(macQueueManagement.MaxSubframesCount, 2304, 'uint8'));
        obj.RetryQueues = repmat(obj.PacketToAggregate, NumStations, macQueueManagement.MaxACs);
        obj.RetryQueueLengths = zeros(NumStations, macQueueManagement.MaxACs);
        obj.RetryFlags = false(NumStations, macQueueManagement.MaxACs);
        obj.RetryMSDUIndices = zeros(NumStations, macQueueManagement.MaxACs, MaxQueueLength);
    end

    function isSuccess = enqueue(obj, station, ac, packet)
        %enqueue Inserts packet into queue
        %
        %   ISSUCCESS = enqueue(OBJ, STATION, AC, PACKET) inserts packet in
        %   the queue maintained for a station in given AC.
        %
        %   ISSUCCESS is a logical value that indicates the status of enqueue.
        %   % 1 - Enqueue success
        %   % 0 - Enqueue fail
        %
        %   STATION is the ID of the station into which packet must be
        %   enqueued.
        %
        %   AC is the access category of the packet.
        %
        %   PACKET is the MSDU to be enqueued.

        % Queue is full
        if obj.TxQueueLengths(station, ac) == obj.MaxQueueLength
            isSuccess = false;

        % Insert packet into transmission queue and increment queue length.
        % Set corresponding MSDU lengths in MSDULengths array.
        else
            index = obj.WriteIndices(station, ac);
            obj.TxQueues(station, ac, index) = packet;
            obj.TxQueueLengths(station, ac) = obj.TxQueueLengths(station, ac) + 1;
            obj.MSDULengths(station, ac, obj.TxQueueLengths(station, ac)) = packet.msduLength;

            obj.WriteIndices(station, ac) = obj.WriteIndices(station, ac) + 1;
            % Reset write index to 1 after reaching maximum queue length.
            if obj.WriteIndices(station, ac) > obj.MaxQueueLength
                obj.WriteIndices(station, ac) = 1;
            end

            isSuccess = true;
        end
    end

    function [txFrame, isSuccess] = dequeue(obj, stationList, acList, numMSDU, numStations)
        %dequeue Dequeues the required number of MSDUs
        %
        %   [TXFRAME, ISSUCCESS] = dequeue(OBJ, STATIONLIST, ACLIST,
        %   NUMMSDU, NUMSTATIOONS) dequeues MSDUs from either transmission
        %   or retry queues.
        %
        %   TXFRAME is the aggregate of frames from all dequeued stations.
        %
        %   ISSUCCESS is a logical value that indicates the status of dequeue.
        %   % 1 - Dequeue success
        %   % 0 - Dequeue fail
        %
        %   STATIONLIST is a 9 x 1 array of station IDs from which packets
        %   must be dequeued.
        %
        %   ACLIST is a 9 x 1 array of access categories corresponding to
        %   the station IDs from which packets must be dequeued.
        %
        %   NUMMSDU is a 9 x 1 array of required number of MSDUs corresponding to
        %   the station IDs from which packets must be dequeued.
        %
        %   NUMSTATIONS is the number of stations for dequeue.

        isSuccess = false(macQueueManagement.MaxMUStations, 1);
        obj.FrameList = repmat(obj.PacketToAggregate, macQueueManagement.MaxMUStations, 1);

        % Index from which MSDU information should be updated in FrameList
        MSDUIdx = 1;

        for idx = 1:numStations

            % Fill number of MSDUs to be dequeued
            obj.FrameList(idx).msduCount = numMSDU(idx);

            % Dequeue from retry queue if it is not empty
            if obj.RetryQueueLengths(stationList(idx), acList(idx)) ~= 0


                % Fill MSDU fields that are in common and dequeue the MSDUs
                % from indices that are not already discarded.
                fillCommonMSDUInfo(obj,stationList(idx), acList(idx), idx, 1);
                numDequeued = dequeueRetryQ(obj, stationList(idx), acList(idx), numMSDU(idx), idx);

                remainingMSDU = numMSDU(idx) - numDequeued;
                % Dequeue from transmission queue if there are less number
                % of MSDUs than desired in retry queue.
                if remainingMSDU > 0
                    MSDUIdx = numMSDU(idx) - remainingMSDU + 1;
                    dequeuedMSDUInfo = dequeueTxQ(obj, stationList(idx), acList(idx), remainingMSDU, idx, MSDUIdx);

                    % Index the MSDUs that are dequeued from transmission
                    % queue.
                    nextRetryMSDUIdx = max(obj.RetryMSDUIndices(stationList(idx), acList(idx), :)) + 1;
                    MSDUIndices = nextRetryMSDUIdx:nextRetryMSDUIdx + remainingMSDU - 1;
                    obj.RetryMSDUIndices(stationList(idx), acList(idx),MSDUIndices) = MSDUIndices;

                    % Insert these MSDUs into retry queue and increment
                    % retry queue length.
                    for index = 1:remainingMSDU
                        obj.RetryQueues(stationList(idx), acList(idx)).timestamp(MSDUIndices(index)) = ...
                            dequeuedMSDUInfo(index).timestamp;
                        obj.RetryQueues(stationList(idx), acList(idx)).msduLength(MSDUIndices(index)) = ...
                            dequeuedMSDUInfo(index).msduLength;
                        obj.RetryQueues(stationList(idx), acList(idx)).data(MSDUIndices(index), :) = ...
                            dequeuedMSDUInfo(index).data;
                        obj.RetryQueueLengths(stationList(idx), acList(idx)) = ...
                            obj.RetryQueueLengths(stationList(idx), acList(idx)) + 1;
                    end

                end
                obj.RetryQueues(stationList(idx), acList(idx)).msduCount = numMSDU(idx);
                isSuccess(idx) = true;

            % Dequeue packets from TxQueues of given station if
            % retry queue is empty
            elseif (obj.TxQueueLengths(stationList(idx), acList(idx)) ~= 0)

                % Fill MSDU fields that are in common and dequeue the MSDUs
                % from transmission queue.
                fillCommonMSDUInfo(obj,stationList(idx), acList(idx), idx, 0);
                dequeueTxQ(obj, stationList(idx), acList(idx), numMSDU(idx), idx, MSDUIdx);

                % Insert the dequeued MSDUs into retry queue and
                % set Retryflag to false.
                enqueueRetryQ(obj, stationList(idx), acList(idx), obj.FrameList(idx));

                % Index the packets in retry queue
                obj.RetryMSDUIndices(stationList(idx), acList(idx),...
                1:obj.RetryQueueLengths(stationList(idx), acList(idx))) =...
                (1:obj.RetryQueueLengths(stationList(idx), acList(idx)))';

                obj.RetryFlags(stationList(idx), acList(idx)) = false;
                obj.RetryQueues(stationList(idx), acList(idx)).msduCount = numMSDU(idx);
                isSuccess(idx) = true;

            % No data in transmission and retry queues
            else
                isSuccess(idx) = false;
            end

        end

        % Aggregate frames from all dequeued stations
        txFrame = obj.FrameList;
    end

    function discardedIndices = discardPackets(obj, stationList, acList, MSDUIndices, numIndices, numStations)
        %discardPackets Discard packets from transmission and retry queues
        %
        %   DISCARDEDINDICES = discardPackets(OBJ, STATIONLIST, ACLIST,
        %   MSDUINDICES, NUMINDICES, NUMSTATIONS) discards packets.
        %
        %   DISCARDEDINDICES is an array of size N x M where N is the
        %   number of stations in the network and M is the maximum subframe
        %   count. It contains the indices of discarded packets from all
        %   stations.
        %
        %   STATIONLIST is a 9 x 1 array of station IDs from which packets
        %   must be discarded.
        %
        %   ACLIST is a 9 x 1 array of access categories corresponding to
        %   the station IDs from which packets must be discarded.
        %
        %   MSDUINDICES is an array of size M x 9 where M is the maximum
        %   subframe count. It contains the indices of packets to be
        %   discarded corresponding to station IDs.
        %
        %   NUMINDICES is a 9 x 1 array of number of indices of packets to
        %   be discarded corresponding to station IDs.
        %
        %   NUMSTATIONS is the number of stations for discard.

        % Initialize
        discardedIndices = zeros(obj.MaxQueueLength, obj.NumStations);

        for staidx = 1:numStations

            % Discard packets if retry queue is not empty.
            if obj.RetryQueueLengths(stationList(staidx), acList(staidx)) ~= 0

                % Find indices that are not already discarded.
                retryMSDUIndices = obj.RetryMSDUIndices(stationList(staidx), acList(staidx), :);

                for MSDUIdx = 1:numIndices(staidx)

                    % Discard packet only if it's index has not been
                    % discarded before
                    if any(retryMSDUIndices == MSDUIndices(MSDUIdx, staidx))

                        % Indices of packets that are discarded are made 0
                        discardIdx = (retryMSDUIndices == MSDUIndices(MSDUIdx, staidx));
                        obj.RetryMSDUIndices(stationList(staidx), acList(staidx), discardIdx) = 0;

                        % Decrement transmission queue length
                        obj.TxQueueLengths(stationList(staidx), acList(staidx)) = ...
                            obj.TxQueueLengths(stationList(staidx), acList(staidx)) - 1;

                        % Decrement retry queue length
                        obj.RetryQueueLengths(stationList(staidx), acList(staidx)) = ...
                            obj.RetryQueueLengths(stationList(staidx), acList(staidx)) - 1;
                    end

                end

                % Indices that are discarded
                tempDiscardedIndices = retryMSDUIndices(ismember(retryMSDUIndices,  MSDUIndices(1:numIndices(staidx), staidx)));
                discardedIndices(1:length(tempDiscardedIndices),stationList(staidx)) = tempDiscardedIndices;

                % Update MSDULengths
                retryIndices = find(retryMSDUIndices);
                indexesToDiscard = ismember(retryIndices, tempDiscardedIndices);
                msduLengths = obj.MSDULengths(stationList(staidx), acList(staidx), :);
                msduLengths(indexesToDiscard) = 0;
                notDiscardedMSDULengths = nonzeros(msduLengths);
                obj.MSDULengths(stationList(staidx), acList(staidx), :) = [notDiscardedMSDULengths;...
                                                zeros(obj.MaxQueueLength - length(notDiscardedMSDULengths), 1)];

            end
        end
    end
end
end
