/** * This file is protected by Copyright. * Please refer to the COPYRIGHT file distributed with this source distribution. * * This file is part of REDHAWK IDE. * * All rights reserved. This program and the accompanying materials are made available under * the terms of the Eclipse Public License v1.0 which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. * */ package nxm.redhawk.prim; import gov.redhawk.sca.util.Debug; import java.io.IOException; import java.net.DatagramPacket; import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.net.NetworkInterface; import java.net.SocketException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.nio.ByteOrder; import java.text.MessageFormat; import java.util.EnumSet; import java.util.Enumeration; import nxm.redhawk.lib.RedhawkOptActivator; import nxm.sys.lib.BaseFile; import nxm.sys.lib.Data; import nxm.sys.lib.DataFile; import nxm.sys.lib.Primitive; /** * @since 8.0 */ public class sourcenic extends Primitive { //SUPPRESS CHECKSTYLE ClassName private static final Debug TRACE_LOGGER = new Debug(RedhawkOptActivator.ID, sourcenic.class.getSimpleName()); private static final int SDDS_HEADER_SIZE = 56; private static final int SDDS_PAYLOAD_SIZE = 1024; private static final int SDDS_PACKET_SIZE = SDDS_HEADER_SIZE + SDDS_PAYLOAD_SIZE; private static final int SDDS_PACKET_ALT_DEFAULT = 0; private static final int DEFAULT_MCAST_PORT = 29495; private static final int DEFAULT_MCAST_READ_TIMEOUT_MS = 100; private static final int BITS_PER_SAMPLE_4 = 4; private static final int BITS_PER_SAMPLE_8 = 8; private static final int BITS_PER_SAMPLE_16 = 16; /** this is used by REDHAWK SinkNic Component to send out float data (32-bit) since SDDS header only allows 5 bit to represent bits per sample (bps). */ private static final int BITS_PER_SAMPLE_32 = 32; /** SDDS packets defaults to using Big Endian for the data's byte order */ private static final ByteOrder DEFAULT_SDDS_DATA_BYTE_ORDER = ByteOrder.BIG_ENDIAN; /** the output file to write to */ private DataFile outputFile = null; private Data outputData; private SDDSHeader sddsHeader; private NetworkInterface ni; private MulticastSocket msock; private InetSocketAddress maddr; private int port; /** statistics for socket read timeouts. */ private long emptyCount; private DatagramPacket packet; /** warning bit fields to display first warning msg of a particular type per instance session. */ private static enum WarnBit { WARN1, WARN2, WARN3 }; private final EnumSet<WarnBit> warnedSet = EnumSet.noneOf(WarnBit.class); private boolean warn; /** is SDDS packet payload data in big-endian/IEEE (network) byte order per spec? or in little-endian/EEEI byte-order. */ private ByteOrder packetDataByteOrder = DEFAULT_SDDS_DATA_BYTE_ORDER; /** * Gets the number of socket read timeouts. * @return */ public long getEmptyCount() { return this.emptyCount; } @Override public int open() { if (TRACE_LOGGER.enabled) { TRACE_LOGGER.enteringMethod(); } warn = MA.getState("/WARN", true); verbose = MA.getState("/VERBOSE", false); int ret = super.open(); if (ret == NORMAL) { // Parse arguments and switches String interfaceBaseName = MA.getS("/INTERFACE", null); String mgrp = MA.getS("/MGRP", null); int vlan = MA.getL("/VLAN", 0); this.port = MA.getL("/PORT", DEFAULT_MCAST_PORT); String fc = MA.getS("/FC", "SI"); int sddsPacketAlt = MA.getL("/ALT", SDDS_PACKET_ALT_DEFAULT); sddsHeader = new SDDSHeader(sddsPacketAlt); outputFile = MA.getDataFile("OUT", "1000", fc, DataFile.OUTPUT, 0, null); outputFile.open(BaseFile.OUTPUT); // allocate data buffer with enough room for SDDS packet header + SDDS data + 8 (bytes extra to detect non-standard SDDS packets) final int numDataElements = SDDS_PAYLOAD_SIZE / outputFile.bpa; outputData = outputFile.getDataBuffer(numDataElements + (SDDS_HEADER_SIZE + 8) / outputFile.bpa); // SUPPRESS CHECKSTYLE MagicNumber outputData.boff = SDDS_HEADER_SIZE; // move byte offset pass SDDS header in outputData's byte buffer outputData.setSize(numDataElements); // reset size to number of SDDS data samples/elements packet = new DatagramPacket(outputData.getBuf(), outputData.buf.length); // use dataBuffer // switch to allow workaround for REDHAWK SinkNic Component sending data in little-endian byte order setByteOrder(MA.getS("/BYTEORDER", DEFAULT_SDDS_DATA_BYTE_ORDER.toString())); // SDDS 4-bit data is packed in NXM IEEE sub-byte order, i,e. byte0:(sample0, sample1), byte1:(sample2, sample3),... if (outputFile.getFormatType() == Data.NIBBLE) { outputFile.setDataRep("IEEE"); outputData.rep = Data.IEEE; } // Make sure the user provided a reasonable network interface if (interfaceBaseName != null) { try { this.ni = getNetworkInterface(interfaceBaseName, vlan); if (this.ni == null) { M.error("Could not locate interface " + interfaceBaseName + " for vlan " + vlan); } if (!this.ni.isUp()) { M.error("Specified interface is not up, cowardly refusing to continue"); } if (!this.ni.supportsMulticast()) { M.error("Specified interface does not support multicast, cowardly refusing to continue"); } } catch (SocketException e) { M.error(e); } } else { // If a vlan was provided attempt to find a valid interface if (vlan > 0) { try { this.ni = findUsableNetworkInterface(vlan); } catch (SocketException e) { M.error(e); } if (this.ni == null) { M.error("Couldn't find usable network interface for vlan, try using the INTERFACE switch"); } } if (verbose) { M.info("Using default multicast interface"); } if (TRACE_LOGGER.enabled) { TRACE_LOGGER.message("Using default multicast interface"); } } // If requested, join the group immediately if (mgrp != null) { if (verbose) { M.info("Joining " + mgrp); } if (TRACE_LOGGER.enabled) { TRACE_LOGGER.message("Joining " + mgrp); } this.setMgrp(mgrp); } } return ret; } @Override public int process() { if (this.msock == null) { return NOOP; } try { try { // this receives packet directly into byte[] at outputData.getBuf() - reducing another array copy msock.receive(packet); } catch (SocketTimeoutException e) { this.emptyCount += 1; return NOOP; } //System.out.println("Packet HA " + packet.getAddress().getHostAddress()); final int len = packet.getLength(); if (len != SDDS_PACKET_SIZE) { warn(WarnBit.WARN1, "Discarding packet of incorrect length {0}", len); return NORMAL; } sddsHeader.parsePacket(packet.getData(), packet.getOffset(), len); final byte packetDataType; final int dataBitSize = sddsHeader.getDataFieldBps(); switch (dataBitSize) { case BITS_PER_SAMPLE_4: packetDataType = Data.NIBBLE; break; case BITS_PER_SAMPLE_8: packetDataType = Data.BYTE; break; case BITS_PER_SAMPLE_16: packetDataType = Data.INT; if (sddsHeader.ss && sddsHeader.isComplex()) { // If necessary, spectral swap/invert complex data intSwap(outputData.buf); } break; case BITS_PER_SAMPLE_32: // to receive REDHAWK SinkNic Component SDDS packets packetDataType = Data.FLOAT; break; default: // return ABORT; // prior to 10.1 warn(WarnBit.WARN2, "Discarding packet of unsupported bit size: {0}", dataBitSize); return NOOP; } if (packetDataType != outputData.getFormatType()) { outputData.setFormatType(packetDataType); } this.outputFile.write(outputData); return NORMAL; } catch (IOException e) { M.warning(e); return ABORT; } } @Override public int close() { if (TRACE_LOGGER.enabled) { TRACE_LOGGER.enteringMethod(); } outputFile.close(); if (this.maddr != null) { try { msock.leaveGroup(this.maddr, this.ni); } catch (IOException e) { M.warning(e); } } return super.close(); } // Controls private void setMgrp(String mgrp) { if ((this.msock != null) && (this.maddr != null)) { try { this.msock.leaveGroup(this.maddr, this.ni); } catch (IOException e) { M.error(e); } this.msock = null; this.maddr = null; } if ((mgrp == null) || mgrp.trim().isEmpty()) { return; } this.maddr = new InetSocketAddress(mgrp, this.port); if (!this.maddr.getAddress().isMulticastAddress()) { M.error("Provided MGRP is not a multicast address"); } try { this.msock = new MulticastSocket(maddr); this.msock.setSoTimeout(DEFAULT_MCAST_READ_TIMEOUT_MS); this.msock.setReuseAddress(true); } catch (IOException e) { M.error(e); } try { this.msock.joinGroup(this.maddr, this.ni); } catch (UnknownHostException e) { M.error(e); } catch (IOException e) { M.error(e); } } // Utility Functions private void byteSwap(byte[] buf) { for (int i = 0; i < buf.length; i += 2) { byte tmp = buf[i]; buf[i ] = buf[i + 1]; buf[i + 1] = tmp; } } /** * Swaps/invert 16-bit integers (2 bytes) in a 32-bit (4 bytes pair) buffer. <br> * buf[0] = buf[2]<br> * buf[1] = buf[3]<br> * buf[2] = orig buf[0]<br> * buf[3] = orig buf[1]<br> * ... * @param buf The buffer to swap byte order */ private void intSwap(byte[] buf) { // CHECKSTYLE:OFF for (int i = 0; i < buf.length; i += 4) { byte tmp1 = buf[i]; byte tmp2 = buf[i + 1]; buf[i ] = buf[i + 2]; buf[i + 1] = buf[i + 3]; buf[i + 2] = tmp1; buf[i + 3] = tmp2; } // CHECKSTYLE:ON } private NetworkInterface getNetworkInterface(String baseName, int vlan) throws SocketException { if (vlan > 0) { return NetworkInterface.getByName(baseName + "." + vlan); } return NetworkInterface.getByName(baseName); } /** * Assuming Linux naming convention "device.vlan" * * @param vlan * @return * @throws SocketException */ private NetworkInterface findUsableNetworkInterface(int vlan) throws SocketException { Enumeration<NetworkInterface> iterator = NetworkInterface.getNetworkInterfaces(); if (iterator == null) { return null; } while (iterator.hasMoreElements()) { NetworkInterface localni = iterator.nextElement(); if (localni.isUp() && localni.supportsMulticast()) { String[] niParts = localni.getName().split("\\."); try { if ((niParts.length == 2) && (Integer.parseInt(niParts[1]) == vlan)) { return localni; } } catch (NumberFormatException e) { // PASS } } } return null; } /** * A crude, partially implemented lightweight SDDS header helper. * * In the absence of a current and readily-available SDDS packet standard, a * number of nearly-(but not quite entirely-)compatible "de facto" standards * for SDDS packets developed. The compile-time SDDS_PACKET_ALT setting allows * users to build copies of this header file that are compatible with these * variations. * * SDDS_MODE_COMPAT - Attempts to accept "du jure" SDDS packets while * also retaining compatibility for SDDS packets * that use the TEN_IN_SIXTEEN_AD mode to indicate * 16-bit complex data. This mode will not work * for "du jure" packets that use the TEN_IN_SIXTEEN_AD mode. * * SDDS_MODE_STRICT - Follows the current "du jure" SDDS packet standard. * * SDDS_MODE_LEGACY_CX1 - Follows a "de facto" standard from the mid- to * late-2000s. In this variant, the TEN_IN_SIXTEEN_AD * data mode is used to indicate 16-bit complex data. * At the time this standard came into use, the cx bit * was not present (i.e. no way to indicate complex * data) and the bit currently used for cx was always * zero. * * SDDS_MODE_LEGACY_CX2 - Follows a "de facto" standard from 2010 that was * based on a mis-interpretation of the "de facto" * SDDS_PACKET_ALT=1 standard. Strictly speaking, this * is a superset of SDDS_PACKET_ALT=1 where FOUR_IN_EIGHT * is unsupported (this is a reasonable assumption * since nearly all digitizers produced in the last * decade use FOUR_IN_FOUR rather than FOUR_IN_EIGHT). * * In this variant, the "former" AD-passthrough-bit is * used as a complex data indicator akin to the cx bit. * The cx bit is ignored in this variant and is always * set to zero. This provides an opportunity for this * variant to read the "du jure" packets (with the cx * bit in use) in addition to the variant packets * provided that the "du jure" packets do not use * FOUR_IN_EIGHT or TEN_IN_SIXTEEN_AD modes. */ class SDDSHeader { static final int DMODE_TEN_IN_SIXTEEN_AD = 0x06; /** REDHAWK SinkNic Component uses dmode=4 and bitsPerSample(bps)=31 (since only 5 bits are used for that field in SDDS header) */ static final int DMODE_FOUR_FOR_32BITS_PER_SAMPLE = 0x04; static final int SDDS_MODE_COMPAT = 0; static final int SDDS_MODE_STRICT = 1; static final int SDDS_MODE_LEGACY_CX1 = 2; static final int SDDS_MODE_LEGACY_CX2 = 3; static final int SDDS_MODE_DEFAULT = SDDS_MODE_COMPAT; // CHECKSTYLE:OFF final int mode; // byte 1 /** is Standard Format (SF) packet */ public boolean sf; /** is Start of Sequence (SoS) */ public boolean sos; /** Parity Packet (PP) */ public boolean pp; /** Original Format (OF) */ public boolean of; /** Spectral Sense (SS) */ public boolean ss; /** Data Mode / data field (DF) */ public byte dmode; // byte 2 /** is CompleX data */ public boolean cx; public boolean snp; public boolean bw; /** Bits Per Sample */ public byte bps; // Frame sequences (byte 3 & byte 4) public short seq; public short msptr; public short msdel; // Time tags public long ttag; public int ttage; // CHECKSTYLE:ON public SDDSHeader() { this(SDDS_MODE_DEFAULT); } public SDDSHeader(int mode) { assert (mode == 0); // TODO implement other modes this.mode = mode; } public void parsePacket(final byte[] data, final int offset, final int len) { if (len != SDDS_PACKET_SIZE) { throw new IllegalArgumentException("Packet data is not the correct size"); } // CHECKSTYLE:OFF final byte byte0 = data[offset + 0]; final byte byte1 = data[offset + 1]; final byte byte2 = data[offset + 2]; final byte byte3 = data[offset + 3]; sf = (byte0 & 0x80) != 0; if (!sf) { warn(WarnBit.WARN3, "Received non-standard packet"); } sos = (byte0 & 0x40) != 0; pp = (byte0 & 0x20) != 0; of = (byte0 & 0x10) != 0; ss = (byte0 & 0x08) != 0; dmode = (byte) (byte0 & 0x07); // 3 bits cx = (byte1 & 0x80) != 0; snp = (byte1 & 0x40) != 0; bw = (byte1 & 0x20) != 0; bps = (byte) (byte1 & 0x1F); // 5 bits seq = byte2; seq = (short) ((seq << 8) | byte3); // CHECKSTYLE:ON } public boolean isComplex() { switch (mode) { case SDDS_MODE_COMPAT: return ((dmode == DMODE_TEN_IN_SIXTEEN_AD) || (cx)); case SDDS_MODE_STRICT: return cx; case SDDS_MODE_LEGACY_CX1: case SDDS_MODE_LEGACY_CX2: return ((dmode == DMODE_TEN_IN_SIXTEEN_AD) || (cx)); default: throw new IllegalStateException("Invalid SDDS packet mode"); } } /** * The data field contains elements of size getDataFieldBps(). * @return */ public int getDataFieldBps() { if (dmode == DMODE_FOUR_FOR_32BITS_PER_SAMPLE && bps == 31) { // REDHAWK SinkNic Component uses this for 32-bit samples - SUPPRESS CHECKSTYLE MagicNumber return BITS_PER_SAMPLE_32; } int x = (dmode & 0x3); // SUPPRESS CHECKSTYLE MAGIC NUMBER switch (x) { case 0: return BITS_PER_SAMPLE_4; case 1: return BITS_PER_SAMPLE_8; case 2: return BITS_PER_SAMPLE_16; default: throw new IllegalStateException("Invalid SDDS packet dmode"); } } } private void warn(final WarnBit warnBit, final String msgFormatPattern, final Object... args) { if (warn) { if (!warnedSet.contains(warnBit)) { // if we have not already warned on this bit field warnedSet.add(warnBit); String msg = MessageFormat.format(msgFormatPattern, args); M.warning(msg); } else if (TRACE_LOGGER.enabled) { TRACE_LOGGER.message("WARN: " + msgFormatPattern, args); } } } /** * @since 10.2 */ public ByteOrder getDataByteOrder() { return packetDataByteOrder; } /** * @since 10.2 */ public void setDataByteOrder(ByteOrder byteOrder) { if (byteOrder == null) { packetDataByteOrder = DEFAULT_SDDS_DATA_BYTE_ORDER; } else { packetDataByteOrder = byteOrder; } if (outputData != null) { if (packetDataByteOrder == ByteOrder.LITTLE_ENDIAN) { outputData.rep = Data.EEEI; } else { // BIG_ENDIAN outputData.rep = Data.IEEE; } } } /** get packet data byte order * @since 10.2 */ public String getByteOrder() { return packetDataByteOrder.toString(); } /** set packet data byte order * @param byteOrderStr possible values: BIG_ENDIAN, LITTLE_ENDIAN, or NATIVE (to use local machine's byte order) * @since 10.2 */ public void setByteOrder(String byteOrderStr) { final ByteOrder newByteOrder; if (ByteOrder.BIG_ENDIAN.toString().equals(byteOrderStr)) { newByteOrder = ByteOrder.BIG_ENDIAN; } else if (ByteOrder.LITTLE_ENDIAN.toString().equals(byteOrderStr)) { newByteOrder = ByteOrder.LITTLE_ENDIAN; } else if ("NATIVE".equals(byteOrderStr)) { newByteOrder = ByteOrder.nativeOrder(); } else { throw new IllegalArgumentException("Invalid data byte order specified: " + byteOrderStr); } setDataByteOrder(newByteOrder); } }