All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.yamcs.simulation.simulator.UdpFrameLink Maven / Gradle / Ivy

package org.yamcs.simulation.simulator;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.tctm.ccsds.error.AosFrameHeaderErrorCorr;
import org.yamcs.tctm.ccsds.error.CrcCciitCalculator;
import org.yamcs.utils.ByteArrayUtils;

import com.google.common.util.concurrent.AbstractScheduledService;

/**
 * Link implementing the TM frames using
 * AOS CCSDS 732.0-B-3
 * TM CCSDS 132.0-B-2
 * USLP CCSDS 732.1-B-1 (TODO)
 * 
 * 
 * Sends frames of predefined size at a configured frequency. If there is no data to send, it sends idle frames.
 * 
 * 
 * @author nm
 *
 */
public class UdpFrameLink extends AbstractScheduledService {
    final String frameType;
    final String host;
    final int port;
    final int frameSize;

    DatagramSocket socket;
    static final int NUM_VC = 3;
    static final int SPACECRAFT_ID = 0xAB;
    final double framesPerSec;
    final static CrcCciitCalculator crc = new CrcCciitCalculator();
    VcBuilder[] builders = new VcBuilder[NUM_VC];
    VcBuilder idleFrameBuilder;

    private static final Logger log = LoggerFactory.getLogger(UdpFrameLink.class);

    int lastVcSent; // switches between 0 and 1 so we don't send always from the same vc

    InetAddress addr;

    public UdpFrameLink(String frameType, String host, int port, int frameLength, double framesPerSec) {
        this.frameType = frameType;
        this.host = host;
        this.port = port;
        this.frameSize = frameLength;
        this.framesPerSec = framesPerSec;

        if ("AOS".equalsIgnoreCase(frameType)) {
            for (int i = 0; i < NUM_VC; i++) {
                builders[i] = new AosVcSender(i, frameLength);
            }
            idleFrameBuilder = new AosVcSender(63, frameLength);
        } else if ("TM".equalsIgnoreCase(frameType)) {
            for (int i = 0; i < NUM_VC; i++) {
                builders[i] = new TmVcSender(i, frameLength);
            }
            idleFrameBuilder = builders[0];
        } else if ("USLP".equalsIgnoreCase(frameType)) {
            for (int i = 0; i < NUM_VC; i++) {
                builders[i] = new UslpVcSender(i, frameLength);
            }
            idleFrameBuilder = new UslpVcSender(63, frameLength);
        } 

    }

    @Override
    protected void startUp() throws Exception {
        addr = InetAddress.getByName(host);
        socket = new DatagramSocket();
    }

    @Override
    protected Scheduler scheduler() {
        return Scheduler.newFixedRateSchedule(0, (long) (1e6 / framesPerSec), TimeUnit.MICROSECONDS);
    }

    @Override
    protected void runOneIteration() throws Exception {
        // dequeue data from all frames
        for (int i = 0; i < NUM_VC; i++) {
            builders[i].dequeue();
        }
        // if one of the first VCs is full, send it
        for (int i = 0; i < 2; i++) {
            int vc = (lastVcSent + i) & 1;
            if (builders[vc].isFull()) {
                sendData(builders[vc]);
                return;
            }
        }

        int mini = -1;
        int minData = Integer.MAX_VALUE;
        // send the first one that has least empty space
        for (int i = 0; i < NUM_VC; i++) {
            if (builders[i].isEmpty()) {
                continue;
            }
            int emptySpace = builders[i].emtySpaceLength();
            if (emptySpace < minData) {
                mini = i;
                minData = emptySpace;
            }
        }
        if (mini != -1) {
            sendData(builders[mini]);
            return;
        }
        // no data available for any VC, send an idle frame
        byte[] idleData = idleFrameBuilder.getIdleFrame();
        socket.send(new DatagramPacket(idleData, idleData.length, addr, port));
    }

    private void sendData(VcBuilder vcb) throws IOException {
        if (!vcb.isFull()) {
            vcb.fillIdlePacket();
        }

        byte[] data = vcb.getFrame();
        socket.send(new DatagramPacket(data, data.length, addr, port));
        vcb.reset();
    }

    /**
     * queue packet for virtual channel
     * 
     * @param vcId
     * @param packet
     */
    public void queuePacket(int vcId, byte[] packet) {
        VcBuilder s = builders[vcId];

        if (!s.queue.offer(packet)) {
            log.warn("dropping packet for virtual channel {} because the queue is full", vcId);
        }
    }

    static abstract class VcBuilder {
        final int vcId;

        protected long vcSeqCount = 0;
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(100);
        protected int dataOffset;

        byte[] pendingPacket;
        int pendingPacketOffset;
        byte[] data;

        boolean firstPacketInFrame = true;
        int firstHeaderPointer = -1;
        int dataEnd;

        public VcBuilder(int vcId) {
            this.vcId = vcId;
            this.dataOffset = hdrSize();
           
        }

        public int emtySpaceLength() {
            return dataEnd - dataOffset;
        }

        public byte[] getFrame() {
            encodeHeaderAndChecksums();
            return data;
        }

        public boolean isEmpty() {
            return dataOffset == hdrSize();
        }

        public boolean isFull() {
            return dataOffset == dataEnd;
        }

        void reset() {
            vcSeqCount++;
            firstPacketInFrame = true;
            dataOffset = hdrSize();
        }

        /**
         * Copy data from the queue into the frame
         * 
         * @param q
         * @return
         * @throws IOException
         */
        void dequeue() throws IOException {
            if (pendingPacket != null) {
                copyPendingToBuffer();
                if (pendingPacket != null) {// not yet fully copied but the frame is full
                    return;
                }
            }
            while (dataOffset < dataEnd) {
                pendingPacket = queue.poll();
                if (pendingPacket == null) {
                    break;
                }
                if (firstPacketInFrame) {
                    firstHeaderPointer = dataOffset - hdrSize();
                    firstPacketInFrame = false;
                }
                pendingPacketOffset = 0;
                copyPendingToBuffer();
                if (pendingPacket != null) {// not yet fully copied but the frame is full
                    break;
                }
            }
        }

        void copyPendingToBuffer() {
            int length = Math.min(pendingPacket.length - pendingPacketOffset, dataEnd - dataOffset);
            log.trace("VC{} writing {} bytes from packet of length {} at offset {}",
                    vcId, length, pendingPacket.length,  dataOffset);
            ;
            System.arraycopy(pendingPacket, pendingPacketOffset, data, dataOffset, length);
            dataOffset += length;
            pendingPacketOffset += length;
            if (pendingPacketOffset == pendingPacket.length) {
                pendingPacket = null;
            }
        }

        private void fillIdlePacket() {
            int n = dataEnd - dataOffset;
            log.trace("VC{} writing idle packet of size {} at offset {}", vcId, n, dataOffset);
            if (n == 0) {
                return;
            } else if (n == 1) {
                data[dataOffset] = (byte) 0xE0;
            } else if (n < 254) {
                data[dataOffset] = (byte) 0xE1;
                data[dataOffset + 1] = (byte) n;
            } else {
                data[dataOffset] = (byte) 0xE2;
                data[dataOffset + 1] = 0;
                ByteArrayUtils.encodeShort(n, data, dataOffset + 2);
            }
            dataOffset += n;
        }

        abstract int hdrSize();

        abstract void encodeHeaderAndChecksums();

        abstract public byte[] getIdleFrame();
    }

    static class AosVcSender extends VcBuilder {

        public AosVcSender(int vcId, int frameSize) {
            super(vcId);
            if ((vcId < 0) || (vcId > 63)) {
                throw new IllegalArgumentException("Invalid virtual channel id " + vcId);
            }

            this.data = new byte[frameSize];
            dataEnd = frameSize - 6;// last 6 bytes are the OCF and CRC
            writeGvcId(data, vcId);

        }

        void writeGvcId(byte[] frameData, int vcId) {
            ByteArrayUtils.encodeShort((1 << 14) + (SPACECRAFT_ID << 6) + vcId, frameData, 0);
        }

        @Override
        int hdrSize() {
            // 2 bytes master channel id
            // 3 bytes virtual channel frame count
            // 1 byte signaling field
            // 2 bytes frame header error control
            // 2 bytes M_PDU header

            // NOTE: there is no insert zone; if there should be any, its size should be added as part of the header
            // size
            return 10;
        }

        @Override
        void encodeHeaderAndChecksums() {
            // set the frame sequence count

            ByteArrayUtils.encode3Bytes((int) vcSeqCount, data, 2);
            data[5] = (byte) (0x60 + ((vcSeqCount >>> 24) & 0xF));

            ByteArrayUtils.encodeShort(firstHeaderPointer, data, 8);
            fillChecksums(data);

        }

        static void fillChecksums(byte[] data) {
            // first Reed-Solomon the header
            int gvcid = ByteArrayUtils.decodeShort(data, 0);
            int x = AosFrameHeaderErrorCorr.encode(gvcid, data[5]);
            ByteArrayUtils.encodeShort(x, data, 6);

            // then overall CRC
            x = crc.compute(data, 0, data.length - 2);
            ByteArrayUtils.encodeShort(x, data, data.length - 2);
        }

        @Override
        public byte[] getIdleFrame() {
            vcSeqCount++;
            encodeHeaderAndChecksums();
            return data;
        }
    }

    static class TmVcSender extends VcBuilder {
        byte[] idleFrameData;
        int ocfFlag = 1;
        
        public TmVcSender(int vcId, int frameSize) {
            super(vcId);
            this.data = new byte[frameSize];
            dataEnd = frameSize - 4 - 2*ocfFlag; // last 6 bytes are the OCF and CRC
            writeGvcId(data, vcId);
        }

        @Override
        int hdrSize() {
            return 6;
        }
        void writeGvcId(byte[] frameData, int vcId) {
            ByteArrayUtils.encodeShort((SPACECRAFT_ID << 4) + (vcId<<1) +ocfFlag, frameData, 0);
        }
        @Override
        void encodeHeaderAndChecksums() {
            // set the frame sequence count
            data[3] = (byte) (vcSeqCount);

            // write the first header pointer
            ByteArrayUtils.encodeShort(firstHeaderPointer, data, 4);
            
            //compute crc
            int x = crc.compute(data, 0, data.length - 2);
            ByteArrayUtils.encodeShort(x, data, data.length - 2);
        }

        @Override
        public byte[] getIdleFrame() {
            ByteArrayUtils.encodeShort(0x7FE, data, 4);
            int x = crc.compute(data, 0, data.length - 2);
            ByteArrayUtils.encodeShort(x, data, data.length - 2);

            vcSeqCount++;

            return data;

        }
    }
    
    
    /**
     * This builds USLP frames with complete primary header, OCF , no insert data, and 32 bits frame count
     *
     */
    static class UslpVcSender extends VcBuilder {
        byte[] idleFrameData;
        int ocfFlag = 1;
        
        public UslpVcSender(int vcId, int frameLength) {
            super(vcId);
            this.data = new byte[frameLength];
            dataEnd = frameLength - 4 - 2*ocfFlag; // last 6 bytes are the OCF and CRC
            
            ByteArrayUtils.encodeInt((12<<28) + (SPACECRAFT_ID << 12) + (vcId<<5), data, 0);

            //frame length
            ByteArrayUtils.encodeShort(frameLength-1, data, 4);
            
            data[6]=0x0C; //ocfFlag = 1, vc frame count = 100(in binary) 
            
        }

        @Override
        int hdrSize() {
            //11 for the primary header (with a 32 bit frame length)
            //3 bytes for the data field header
            return 14;
        }

        @Override
        void encodeHeaderAndChecksums() {
            // set the frame sequence count
            ByteArrayUtils.encodeInt((int)vcSeqCount, data, 7);

            // write the first header pointer
            ByteArrayUtils.encodeShort(firstHeaderPointer, data, 12);
            
            //compute crc
            int x = crc.compute(data, 0, data.length - 2);
            ByteArrayUtils.encodeShort(x, data, data.length - 2);
        }

        @Override
        public byte[] getIdleFrame() {
            vcSeqCount++;
            encodeHeaderAndChecksums();
            return data;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy