package nxm.sys.net; //not needed for non-nxm use

import java.net.BindException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;
// Begin Since 3.5.4 Bug 2913 add Java encryption related imports //////
import java.security.Key;
import java.security.spec.AlgorithmParameterSpec;
import java.util.zip.Deflater;

import javax.crypto.Cipher;
import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory;
// End since 3.5.4 for Bug 2913 ////////////////////////////////////////
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import nxm.sys.inc.InternalUseOnly;
import nxm.sys.lib.Data; //for format
import nxm.sys.lib.ICEPacket; //for RTP Header of ICE Packet
import nxm.sys.lib.Shell; //for rep in IcePacket Header
import nxm.sys.lib.Time; //for current Time

//import static nxm.net.lib.DatagramData.COMPOUND_PACKET;  // referenced directly to avoid extra deprecation warnings
//import static nxm.net.lib.DatagramData.ICE1_PACKET;
//import static nxm.net.lib.DatagramData.ICE2_PACKET;
//import static nxm.net.lib.DatagramData.ICE3_PACKET;      // referenced directly to avoid extra deprecation warnings
//import static nxm.net.lib.DatagramData.SDDS_PACKET;
//import static nxm.net.lib.DatagramData.XSTREAM_PACKET;
//import static nxm.net.lib.DatagramData.MAC_PACKET;       // referenced directly to avoid extra deprecation warnings

/**
  dgnetsend - outputs Multicast messages. May choose multicast or udp but do not mix.
  @deprecated Use {@link nxm.net.lib.dgnetsend} instead - since 3.7.2
  @author  MSZ / JPH
*/
@Deprecated
public class dgnetsend {
   //settable by accessor methods
  private boolean verbose = false;
  private boolean packetDebug = false;
  private boolean multicast = true;
  private boolean keepopen=false;

  private Deflater compressor = null;
  // add  encryption related private fields - since 3.5.4 bug 2921
  private Cipher cipher = null;
  private Key cipherkey = null;
  private PBEKeySpec pbeKeySpec=null;
  private AlgorithmParameterSpec pbeParamSpec = null;
  private String ciphertype="PBEWithSHA1AndDESede"; //PBEWithMD5AndDES,PBEWithMD5AndTripleDES,PBEWithSHA1AndRC2

  private Mac mac = null;
  private byte[] signature = new byte[20]; //128(16byte) bit MD5, 160bit(20byte) SHA1, 256 bit (32byte) SHA-256
  private static final byte[] fakesignature="00000000000000000000".getBytes(); //20 0's
  private int ITERATIONS = 1000;
  private SecretKeySpec signingkey = null;
  private String hmactype="HmacSHA1"; //HmacMD5(128bit),HmacSHA1(160bit),HmacSHA256,HmacSHA384,HmacSHA512

  private String host=new String("238.0.0.1"); // remote host
  private int port=18800;                      // remote port
  private int ttl=1; //ttl is unsigned 8-bit, 0 <= ttl <= 0xFF
  private int rtphdrlen=0;
  private int icehdrlen=0;
  private int ssrc=0,csrc0=0;
  private short payload=0;

  //private variables
  private static final int MAXPKTBUF=65536; //max data
  /** RTP Header without CSRC size in bytes */
  private static final int RTP_HEADER_SIZE = 12;      // since 3.7.2
  /** RTP Header - CSRC entry size in bytes */
  private static final int RTP_CSRC_SIZE = 4;         // since 3.7.2
  /** RTP 32-bit word containing extended header id and length */
  private static final int RTP_XHDR_HEADER_SIZE = 4;  // since 3.7.2
  private InetAddress addr=null;
  private DatagramSocket dsock=null;
  private MulticastSocket msock=null;
  private String osname = (System.getProperty("os.name")).toUpperCase(); // MSZ work-around for windows
  private boolean windows = (osname.startsWith("WINDOWS"));
  private DatagramPacket dpacki = null;

  /** ICE packet handling */
  ICEPacket icePacket = new ICEPacket("SB", ICEPacket.headerLength, 0);

  /** Max 16-bit (2 byte) unsigned integer. Value = 2<sup>16</sup> = {@value}. */
  static final int  MAX_UNSIGNED_INT_2 = 65535;
  /** Max 32-bit (4 byte) unsigned integer. Value = 2<sup>32</sup> = {@value}. */
  static final long MAX_UNSIGNED_INT_4 = 4294967296L;
  /** Max 32-bit (4 byte) signed integer. Value = 2<sup>31</sup> - 1 = {@value}. */
  static final int  MAX_INT_4  = Integer.MAX_VALUE;

  /** max size for packet */
  public static final int MAXPKT=32768;
  private byte[] rtpbuf = new byte[MAXPKT];
  private int timestamp=0;
  private int compsize=0;  // Since 3.5.4 Bug 2921
  int seqnum=0;
  int count=0; // TODO: count should be 'long' to cover max 32-bit unsigned integer

  /** dgnetsend Default Constructor */
  public dgnetsend() { }
  /** dgnetsend Constructor
   *  @param phost the remote host
   *  @param pport the remote port
   */
  public dgnetsend(String phost,int pport) {
    host=phost;
    port=pport;
  }

  /** Get the next sequence number to use
      @return next sequence number to use
      @since NeXtMidas 2.8.2
   */
  public int getNextSeqNum() {
    if (seqnum>=MAX_UNSIGNED_INT_2) return 0;
    return (seqnum+1);
  }

  /** Get the current sequence number
      @return current sequence number
      @since NeXtMidas 2.8.2
   */
  public int getSeqNum() { return seqnum; }

  /** Set the current sequence number
      @param parm the new sequence number to use
      @since NeXtMidas 2.8.2
   */
  public void setSeqNum(int parm) { seqnum=parm; }

  /** Get the time-to-live (TTL)
   *  @return time-to-live */
  public int getTTL() { return ttl; }
  /** Set the time-to-live (TTL)
   *  @param pittl time-to-live */
  public void setTTL(int pittl) {
    try {
      ttl=pittl;
      if (msock==null) return;
      if (multicast) msock.setTimeToLive(ttl);
      if (verbose) System.out.println("dgnetsend: setting ttl "+ttl);
    } catch (Exception e) {
      System.err.println("dgnetsend: Invalid ttl="+pittl+". TTL Must be >=0 and <=255");
      e.printStackTrace();
    }
  }

  /** @return 0
   *  @deprecated Since NeXtMidas 2.5.5: see Bug 1367 */
  @Deprecated public int getTimeOut() { return 0; }
  /** @param pTimeOut not used - this method does not do ANYTHING!
   *  @deprecated Since NeXtMidas 2.5.5: see Bug 1367  */
  @Deprecated public void setTimeOut(int pTimeOut) { System.out.println("dgnetsend: setTimeOut() deprecated: did nothing"); }
  /** Gets the current state of verbose output
   *  @return the current state of verbose output
   */
  public boolean getVerbose() { return verbose; }
  /** Sets the state of verbose output
   *  @param pVerbose the state of verbose output
   */
  public void setVerbose(boolean pVerbose) {
    verbose=pVerbose;
    if (verbose) System.out.println("dgnetsend: setting Verbose "+pVerbose);
  }
  /** Gets the current state of packet debugging
   *  @return the current state of packet debugging
   */
  public boolean getPacketDebug() { return packetDebug; }
  /** Sets the state of packet debugging
   *  @param pPacketDebug the state of packet debugging
   */
  public void setPacketDebug(boolean pPacketDebug) {
    packetDebug=pPacketDebug;
    if (verbose) System.out.println("dgnetsend: setting PacketDebug "+pPacketDebug);
  }
  /** Sets the remote host
   *  @param phost the remote host
   */
  public void setHost(String phost) {
    host=phost;
    if (verbose) System.out.println("dgnetsend: setting host "+phost);
  }
  /** Gets the remote host
   *  @return the remote host
   */
  public String getHost() { return host; }
  /** Sets the remote port
   *  @param pport the remote port
   */
  public void setPort(int pport) {
    port=pport;
    if (verbose) System.out.println("dgnetsend: setting port "+pport);
  }
  /** Gets the remote port
   *  @return the remote port
   */
  public int getPort() { return port; }
  /** Sets the state of keepopen, which means writing messages to anywhere so open both sockets
   *  because host may be multicast or udp
   *  @param pkeepopen the state of keepopen
   */
  public void setKeepOpen(boolean pkeepopen) {
    keepopen=pkeepopen;
    if (verbose) System.out.println("dgnetsend: setting keepopen "+pkeepopen);
  }
  /** Get payload type
   *  @return the payload type
   */
  public short getPayload() { return payload; }
  /** Set payload type
   *  @param ppayload the payload type
   */
  public void setPayload(short ppayload) {
    payload=ppayload;
    if (verbose) System.out.println("dgnetsend: setting payload "+payload);
  }
  /** Get synchronization source (SSRC) identifier
   *  @return the synchronization source (SSRC) identifier
   */
  public int getSSRC() { return ssrc; }
  /** Set synchronization source (SSRC) identifier
   *  @param pssrc the synchronization source (SSRC) identifier
   */
  public void setSSRC(int pssrc) {
    ssrc=pssrc;
    if (verbose) System.out.println("dgnetsend: setting ssrc "+ssrc);
  }
  /** Get contributing source (CSRC) identifier
   *  @return the contributing source (CSRC) identifier
   *          (only one currently supported)
   */
  public int getCSRC() { return csrc0; }
  /** Set contributing source (CSRC) identifier
   *  @param csrc0id the contributing source (CSRC) identifier
   *                 (only one currently supported)
   */
  public void setCSRC(int csrc0id) {
    csrc0=csrc0id;
    if (verbose) System.out.println("dgnetsend: setting csrc(0) "+csrc0);
  }
  /** Is an active compressor being used?
   *  @return whether or not an active compressor is being used
   */
  public boolean getCompress() { if (compressor!=null) return true; else return false; }
  /** Sets a new compressor or unsets the current compressor
   *  @param pcompress the state of whether or not to use a compressor
   */
  public void setCompress(boolean pcompress) {
    if (pcompress) {
      compressor=new Deflater(Deflater.BEST_COMPRESSION); // in 3.5.4: passed in BEST_COMPRESSION for Bug 2921
    } else {
      compressor=null;
    }
    if (verbose) System.out.println("dgnetsend: setting compress "+pcompress);
  }

  /** Gets if the Message Authentication Code Algorithm has been set
   *  @return true if mac is not null
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public boolean getMac() {  return (mac!=null); }

  /** Sets the Message Authentication Code Algorithm
   *  @param dpmac 1 for MAC null, otherwise set MAC based on key
   *  @param key   key
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public void setMac(int dpmac,String key) {
    if (dpmac==1) setMac(false,key);
    else setMac(true,key);
  }
  /** Sets the Message Authentication Code Algorithm
   *  @param pmac true:  set MAC based on key
   *              false: set MAC to null
   *  @param key  key
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public void setMac(boolean pmac,String key) {
    if (verbose) System.out.println("dgnetsend: setting Mac "+pmac);
    if (!pmac) {
      mac=null;
      return;
    }
    try {
      mac=Mac.getInstance(hmactype);
      signingkey=new SecretKeySpec(key.getBytes(),hmactype);
      mac.init(signingkey);
    } catch (Exception e) {
      e.printStackTrace();
      mac=null;
    }
  }
  /** Gets if encryption has been set
   *  @return true if cipher is not null
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public boolean getEncrypt() { return (cipher!=null); }

  /** Set encryption
   *  @param dpcipher 1 - cipher is null, otherwise set cipher based on key
   *  @param key      key used to create encryption key
   *  @param salt     the salt
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public void setEncrypt(int dpcipher,String key,String salt) {
    if (dpcipher==1) setEncrypt(false,key,salt);
    else setEncrypt(true,key,salt);
  }

  /** Sets the Encryption Cipher
   *  @param pcipher true: set cipher based on key and salt
   *                 false: set cipher to null
   *  @param key     key used to create encryption key
   *  @param salt    the salt
   *  @since NeXtMidas 3.5.4 for Bug 2921
   */
  public void setEncrypt(boolean pcipher,String key,String salt) {
    if (verbose) System.out.println("dgnetsend: setting Encrypt "+pcipher);
    if (!pcipher) {
      cipher=null;
      return;
    }
    try {
      //java.security.Provider[] providers = java.security.Security.getProviders();
      //java.util.Set<java.security.Provider.Service> pset = null;
      //for (int i=0;i<providers.length;i++) {
      //  System.out.println("P"+i+"="+providers[i]);
      //  pset=providers[i].getServices();
      //  for (java.security.Provider.Service pset2 : pset) {
      //    System.out.println("S="+pset2);
      //  }
      //}
      if (verbose) System.out.println("dgnetsend: setEncrypt with "+key+" and "+salt);
      pbeParamSpec=new PBEParameterSpec(salt.getBytes(),ITERATIONS);
      pbeKeySpec=new PBEKeySpec(key.toCharArray());
      cipherkey=SecretKeyFactory.getInstance(ciphertype).generateSecret(pbeKeySpec);
      cipher=Cipher.getInstance(cipherkey.getAlgorithm());
      cipher.init(Cipher.ENCRYPT_MODE,cipherkey,pbeParamSpec);
      if (verbose) System.out.println("Done setEncrypt");
    } catch (Exception e) {
      e.printStackTrace();
      cipher=null;
    }
  }
  /** Enable/Disable the local loopback of multicast datagrams.
      @param disable true to disable the local loopback of multicast datagrams
      @see MulticastSocket#setLoopbackMode(boolean)
      @since NeXtMidas 3.0.0
   */
  public void setLoopbackMode(boolean disable) {
    if (verbose) System.out.println("dgnetsend: setting LookbackMode "+disable);
    if (msock==null) return;
    try {
      msock.setLoopbackMode(disable);
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
  /** Is the local loopback of multicast datagrams enabled/disabled?
      @return  true if the LoopbackMode has been disabled
      @see MulticastSocket#getLoopbackMode()
      @since NeXtMidas 3.0.0
   */
  public boolean getLoopbackMode() {
    if (msock==null) return false;
    try {
      return msock.getLoopbackMode();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }
  /** Gets the Multicast Socket in use
   *  @return the Multicast Socket
   *  @since NeXtMidas 3.0.0
   */
  public MulticastSocket getMulticastSocket() { if  (multicast) return msock; return null; }
  /** Gets the Datagram Socket in use
   *  @return the Datagram Socket
   *  @since NeXtMidas 3.0.0
   */
  public DatagramSocket getDatagramSocket()   { if (!multicast) return dsock; return null; }

  ////////////////////////////////////////////////////////////////////////////////
  /** Calls {@link #open()} to open a new Multicast and Datagram Socket
   *  @param phost   the host address
   *  @param pport   the port
   *  @param pptype  the payload type
   *  @param pssrc   the synchronization source (SSRC) identifier
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return whether or not the Multicast and Datagram Sockets were opened
   */
  public boolean open(String phost,int pport,short pptype,int pssrc,int csrc0id) {
    host=phost;
    port=pport;
    payload=pptype;
    ssrc=pssrc;
    csrc0=csrc0id;
    keepopen=true;
    return open();
  }
  ////////////////////////////////////////////////////////////////////////////////
  /** Calls {@link #open()} to open a new Multicast and Datagram Socket
   *  @param phost the host address
   *  @param pport the port
   *  @return whether or not the Multicast and Datagram Sockets were opened
   */
  public boolean open(String phost,int pport) {
    host=phost;
    port=pport;
    keepopen=true;
    return open();
  }
  /** Calls {@link #open()} to open a new Multicast and Datagram Socket
   *  @param pptype  the payload type
   *  @param pssrc   the synchronization source (SSRC) identifier
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return whether or not the Multicast and Datagram Sockets were opened
   */
  public boolean open(short pptype,int pssrc,int csrc0id) {
    payload=pptype;
    ssrc=pssrc;
    csrc0=csrc0id;
    keepopen=true;
    return open();
  }
  ////////////////////////////////////////////////////////////////////////////////
  /** Opens a new Multicast Socket and Datagram Socket
   *  @return whether or not the Multicast and Datagram Sockets were opened
   */
  public boolean open() {
    try {
      close();
      if (!keepopen) { //!keepopen means writing messages to anywhere so open both sockets because host may be multicast or udp
        msock = new MulticastSocket();
        msock.setTimeToLive(ttl);
        dsock = new DatagramSocket();
      } else {
        if (host==null) { System.err.println("dgnetsend: No Host to open"); return false; }

        InetSocketAddress isa = new InetSocketAddress(host,port);
        addr = InetAddress.getByName(host);
        multicast = addr.isMulticastAddress(); // e.g. 224.0.0.0 through 239.255.255.255 for IPv4
        if (verbose && multicast) System.out.println("dgnetsend: Set Multicast on for "+isa);

        if (multicast) {
          // MSZ: work-around for Java Bug 4701650 on Windows (09JAN06), NeXtMidas Bug 299
          //        msock = new MulticastSocket(isa);
          // Brought this back in as it passes the scenario from a jar file KME since 3.9.0
          msock =  new MulticastSocket(port);
          msock.joinGroup(addr);
//          if (windows) { msock = new MulticastSocket(port); }
//          else         { msock = new MulticastSocket(isa); }
//                           }
          msock.setTimeToLive(ttl);
        } else dsock = new DatagramSocket(); // Bug 1969 (for unicast, on any available port); previously used isa (which is remote host/port)
        if (verbose) System.out.println("dgnetsend: Connected to Socket. Host:"+host+". Port:"+port);
      }
      if (verbose) System.out.println("dgnetsend: Done open. dsock=" +dsock+" msock="+msock);
      return true;
    } catch (Exception e) {
      System.err.println("dgnetsend: Open Error:"+e+". For IP="+host+". Port="+port);
      if (e instanceof BindException && "Cannot assign requested address".equals(e.getMessage())) { // nice info msg for Bug 1864
        System.err.println("dgnetsend: On RHEL4+ (system with dual IPv4 and IPv6 stacks), try defining\n"
                         + "           java.net.preferIPv4Stack=true system property to use IPv4 stack.");
      }
      e.printStackTrace();
    }
    return false;
  }
  ////////////////////////////////////////////////////////////////////////////////
  /** Close Datagram and Multicast Sockets */
  public void close() {
    if (verbose) System.out.println("dgnetsend: Closing");
    try {
      if (dsock!=null) { dsock.close(); dsock=null; }
      if (msock!=null) { msock.leaveGroup(addr); msock.close(); msock=null; }
    } catch (Exception e) {
      System.err.println("dgnetsend: Exception: "+e);
      e.printStackTrace();
    }
  }
  //writeRTP methods - String input
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param sbuf the buffer as a string
   *  @return whether or not the packet was successfully written to
   */
  public boolean writeRTP(String sbuf) {
    if (sbuf==null || sbuf.length()<1) { // since 3.5.4 for Bug 2921
      System.err.println("dgnetsend: writeRTP Null Input"); return false;
    }
    try {
      byte[] buf = sbuf.getBytes(); // TODO: String.getBytes() never return null, so below check is not needed?
      if (buf==null) { System.err.println("dgnetsend: writeRTP Null Input"); return false; }
      return writeRTPICE(sbuf); // TODO: should this call
    } catch (Exception e) {
      System.err.println("dgnetsend: writeRTP Exception: "+e);
      e.printStackTrace();
    }
    return false;
  }
  ////////////////////////////////////////////////////////////////////////////////////////////////
  //Testing calls
  ////////////////////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param sbuf buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTP_T1(String sbuf) {
      byte[] buf = sbuf.getBytes();
      return writeRTP(buf,buf.length);
  }
  /** Write an RTP packet
   *  @param sbuf buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTP_T2(String sbuf) {
      byte[] buf = sbuf.getBytes();
      return writeRTP(buf,buf.length-1,1);
  }
  /** Write an RTP packet
   *  @param sbuf buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTP_T3(String sbuf) {
      byte[] buf = sbuf.getBytes();
      return writeRTP(buf,buf.length,(short)15,1234);
  }
  /** Write an RTP packet
   *  @param sbuf buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTP_T4(String sbuf) {
      byte[] buf = sbuf.getBytes();
      return writeRTP(buf,buf.length-2,(short)16,1236,2);
  }
  /** Write an RTP packet
   *  @param sbuf buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTP_T5(String sbuf) {
      byte[] buf = sbuf.getBytes();
      return writeRTP(buf,buf.length-3,(short)17,1237,3,2345);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T1(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,short ptype,int ssrcid);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length,(short)18,1238);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T2(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length,1234567.0,11.0,12.0);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T3(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,int offset);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length-1,1234568.0,13.0,14.0,1);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T4(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,short ptype,int ssrcid,int offset);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length-1,1234568.0,15.0,16.0,(short)19,1239,1);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T5(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,double jtime,float p1,float p2,float p3,float p4,short ptype,int ssrcid,int offset,int csrc0id);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length-1,1234569.0,17.0f,18.0f,19.0f,20.0f,(short)20,1240,1,100);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T6(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,short ptype,int ssrcid,int offset,int csrc0id);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length-2,1234570.0,21.0,22.0,(short)21,1241,2,101);
  }
  /** Write an RTP packet with an ICE Header
   *  @param sbuf buffer as a string
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE_T7(String sbuf) {
  //boolean writeRTPICE(byte[] dbuf,int size,String format,double jtime,double p1,double p2,short ptype,int ssrcid,int offset,int csrc0id);
      byte[] buf = sbuf.getBytes();
      return writeRTPICE(buf,buf.length-3,"SB",1234571.0,23.0,24.0,(short)22,1242,3,1000);
  }
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param sbuf   buffer as a String
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @return true if successful
   */
  public boolean writeRTP(String sbuf,short ptype,int ssrcid) { //matches old calls
    if (sbuf==null || sbuf.length()<1) { // since 3.5.4 for Bug 2921
      System.err.println("dgnetsend: writeRTP Null Input"); return false;
    }
    try {
      byte[] buf = sbuf.getBytes();
      // TODO: String.getBytes() never return null, so below check is not needed?
      if (buf==null) { System.err.println("dgnetsend: writeRTP Null Input"); return false; }
      // return writeRTP(buf,buf.length,ptype,ssrcid,0,csrc0);        // prior to 3.5.4
      return writeRTPCommon(buf,buf.length,null,0,ptype,ssrcid,0,csrc0); // since 3.5.4 for Bug 2921
    } catch (Exception e) {
      System.err.println("dgnetsend: writeRTP Exception: "+e);
      e.printStackTrace();
    }
    return false;
  }
  //writeRTP methods - byte input
  ////////////////////////////////////////////////////////////////////////////////
  /** Calls {@link #writeRTPCommon(byte[], int, byte[], int, short, int, int, int)} to
   *  write an RTP packet
   *  @param dbuf RTP Payload / data
   *  @param size size of payload in bytes
   *  @return true if successful
   */
  public boolean writeRTP(byte[] dbuf,int size) { // updated in 3.5.4 Bug 2921 to use writeRTPICE(..) vs writeRTP(..)
         return writeRTPCommon(dbuf,size,null,0,payload,ssrc,0,csrc0); }
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param offset start offset of the data
   *  @return true if successful
   */
  public boolean writeRTP(byte[] dbuf,int size,int offset) { // updated in 3.5.4 Bug 2921 to use writeRTPICE(..) vs writeRTP(..)
         return writeRTPCommon(dbuf,size,null,0,payload,ssrc,offset,csrc0) ; }
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @return true if successful
   */
  public boolean writeRTP(byte[] dbuf,int size,short ptype,int ssrcid) { // updated in 3.5.4 Bug 2921 to use writeRTPICE(..) vs writeRTP(..)
         return writeRTPCommon(dbuf,size,null,0,ptype,ssrcid,0,csrc0); }
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @param offset start offset of the data
   *  @return true if successful
   */
  public boolean writeRTP(byte[] dbuf,int size,short ptype,int ssrcid,int offset) { // updated in 3.5.4 Bug 2921 to use writeRTPICE(..) vs writeRTP(..)
         return writeRTPCommon(dbuf,size,null,0,ptype,ssrcid,offset,csrc0); }
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet
   *  @param dbuf    RTP Payload / data
   *  @param size    size of payload in bytes
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param offset  start offset of the data
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTP(byte[] dbuf,int size,short ptype,int ssrcid,int offset,int csrc0id) { // updated in 3.5.4 Bug 2921 to use writeRTPICE(..) vs writeRTP(..)
         return writeRTPCommon(dbuf,size,null,0,ptype,ssrcid,offset,csrc0id); }

  ////////////////////////////////////////////////////////////////////////////////
  //writeRTPICE methods where hdr is passed in the byte buffer
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet with an ICE header
   *  @param dbuf  RTP Payload / data
   *  @param size  size of payload in bytes
   *  @param ptype payload type
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,short ptype) { //contains ICE Hdr in dbuf - updated in 3.5.4 for Bug 2921
     //List.subList(Arrays.asList(dbuf),0,ICEPacket.headerLength).toArray();
     System.arraycopy(dbuf,0,icePacket.buf,0,ICEPacket.headerLength);
     return writeRTPCommon(dbuf,size-ICEPacket.headerLength,icePacket.buf,ICEPacket.headerLength,ptype,ssrc,
                        ICEPacket.headerLength,csrc0);
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /** Writes an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param size    size of payload in bytes
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,short ptype,int ssrcid,int csrc0id) { //contains ICE Hdr in dbuf -updated in 3.5.4 Bug 2921
     System.arraycopy(dbuf,0,icePacket.buf,0,ICEPacket.headerLength);
     return writeRTPCommon(dbuf,size-ICEPacket.headerLength,icePacket.buf,ICEPacket.headerLength,ptype,ssrcid,
                        ICEPacket.headerLength,csrc0id);
  }
  ////////////////////////////////////////////////////////////////////////////////
  //writeRTPICE methods which have iceheader passed in sep buf
  ////////////////////////////////////////////////////////////////////////////////
  /** Writes an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param icebuf  ICE header buffer
   *  @param size    size of payload in bytes
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @param offset  start offset of the data
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,byte icebuf[],int size,short ptype,int ssrcid,int csrc0id,int offset) { //contains ICE Hdr in sep buffer
    return writeRTPCommon(dbuf,size,icebuf,icebuf.length,ptype,ssrcid,offset,csrc0id);
  }
  //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  /** Writes an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param icebuf  ICE header buffer
   *  @param size    size of payload in bytes
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,byte icebuf[],int size,short ptype,int ssrcid,int csrc0id) { //contains ICE Hdr in sep buffer
    return writeRTPCommon(dbuf,size,icebuf,icebuf.length,ptype,ssrcid,0,csrc0id);
  }

  ////////////////////////////////////////////////////////////////////////////////
  //writeRTPICE methods which create ICE header
  ////////////////////////////////////////////////////////////////////////////////
  /** Write an RTP packet with an ICE header
   *  @param sbuf the buffer as a String
   *  @return true if successful
   */
  @InternalUseOnly("since initial version in NeXtMidas 3.5.4")
  public boolean writeRTPICE(String sbuf) {
    if (sbuf == null) { System.err.println("dgnetsend: writeRTPICE Null Input"); return false; }
    byte[] buf = sbuf.getBytes(); // TODO: String.getBytes() probably will never return null, so below check is not needed?
    if (buf==null) { System.err.println("dgnetsend: writeRTPICE Null Input"); return false; }
    return writeRTPICE(buf,buf.length,Time.current(),3.0,5.0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,short ptype,int ssrcid) {
    return writeRTPICE(dbuf,size,Time.current(),0.0,0.0,ptype,ssrcid,0,csrc0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf  RTP Payload / data
   *  @param size  size of payload in bytes
   *  @param jtime timecode in Julian time for ICE packet header
   *  @param p1    first data value for ICE packet header
   *  @param p2    second data value for ICE packet header
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2) {
    return writeRTPICE(dbuf,size,jtime,p1,p2,payload,ssrc,0,csrc0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param jtime  timecode in Julian time for ICE packet header
   *  @param p1     first data value for ICE packet header
   *  @param p2     second data value for ICE packet header
   *  @param offset start offset of the data
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,int offset) {
    return writeRTPICE(dbuf,size,jtime,p1,p2,payload,ssrc,offset,csrc0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param jtime  timecode in Julian time for ICE packet header
   *  @param p1     first data value for ICE packet header
   *  @param p2     second data value for ICE packet header
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,short ptype,int ssrcid) {
    return writeRTPICE(dbuf,size,jtime,p1,p2,ptype,ssrcid,0,csrc0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf   RTP Payload / data
   *  @param size   size of payload in bytes
   *  @param jtime  timecode in Julian time for ICE packet header
   *  @param p1     first data value for ICE packet header
   *  @param p2     second data value for ICE packet header
   *  @param ptype  payload type
   *  @param ssrcid synchronization source (SSRC) identifier
   *  @param offset start offset of the data
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,short ptype,int ssrcid,int offset) {
    return writeRTPICE(dbuf,size,jtime,p1,p2,ptype,ssrcid,offset,csrc0);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param size    size of payload in bytes
   *  @param jtime   timecode in Julian time for ICE packet header
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param p3      third data value for ICE packet header
   *  @param p4      fourth data value for ICE packet header
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param offset  start offset of the data
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,float p1,float p2,float p3,float p4,short ptype,int ssrcid,int offset,int csrc0id) {
    icehdrlen = defICE(icePacket.buf,count,size,jtime,p1,p2,p3,p4,0);
    return writeRTPCommon(dbuf,size,icePacket.buf,icehdrlen,ptype,ssrcid,offset,csrc0id);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param size    size of payload in bytes
   *  @param jtime   timecode in Julian time for ICE packet header
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param offset  start offset of the data
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,double jtime,double p1,double p2,short ptype,int ssrcid,int offset,int csrc0id) {
    if (count>=MAX_INT_4) count=0;
    else count++;
    icehdrlen = defICE(icePacket.buf,count,size,"SB",jtime,p1,p2,0);
    return writeRTPCommon(dbuf,size,icePacket.buf,icehdrlen,ptype,ssrcid,offset,csrc0id);
  }
  /** Write an RTP packet with an ICE header
   *  @param dbuf    RTP Payload / data
   *  @param size    size of payload in bytes
   *  @param format  Midas format digraph (e.g. "SB", "SI", etc.)
   *  @param jtime   timecode in Julian time for ICE packet header
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param ptype   payload type
   *  @param ssrcid  synchronization source (SSRC) identifier
   *  @param offset  start offset of the data
   *  @param csrc0id contributing source (CSRC) identifier
   *                 (only one currently supported)
   *  @return true if successful
   */
  public boolean writeRTPICE(byte[] dbuf,int size,String format,double jtime,double p1,double p2,short ptype,int ssrcid,int offset,int csrc0id) {
    if (count>=MAX_INT_4) count=0;
    else count++;
    icehdrlen = defICE(icePacket.buf,count,size,format,jtime,p1,p2,0);
    return writeRTPCommon(dbuf,size,icePacket.buf,icehdrlen,ptype,ssrcid,offset,csrc0id);
  }
  /**
   * Write RTP Packet with or without optional extended headers
   *
   * Name changed from writeRTPICE to writeRTPCommon in 3.7.2 for clarification
   *
   * @param databuf     RTP Payload / data
   * @param size        size of payload in bytes
   * @param icebuf      ICE buffer (or null if none)
   * @param icehdrlen   length of ICE buffer (or 0 if none)
   * @param ptype       payload type
   * @param ssrcid      synchronization source (SSRC) identifier
   * @param offset      start offset of the data
   * @param csrc0id     contributing source (CSRC) identifier
   *                    (only one currently supported)
   * @return            true if successful
   * @since 3.5.4 (Bug 2921) to place code in common 'helper' method
   */
  private boolean writeRTPCommon(byte[] databuf,int size,byte[] icebuf,
      int icehdrlen,short ptype,int ssrcid,int offset,int csrc0id) {
    try {
      timestamp+=size; //size already takes into account -icehdrlen
      if (seqnum>=MAX_UNSIGNED_INT_2) seqnum=0;
      else seqnum++;

      //-------- CREATE RTP HEADER (including extended headers IF available) -------
      // next if/else add in 3.5.4 for Bug 2921 to support multiple extended headers
      // this is only for ordering, if icebuf is null but mac is not, then mac needs to be first
      // if mac is null but icebuf is not, then need icebuf first and only,
      // hmac is the fakesignature
      if (mac!=null) {
        rtphdrlen=setRTPHeader(rtpbuf,fakesignature,fakesignature.length,(byte)nxm.sys.net.DatagramData.MAC_PACKET,
                                      icebuf,icehdrlen,(byte)nxm.sys.net.DatagramData.ICE3_PACKET,
                                      ptype,ssrcid,seqnum,timestamp,csrc0id);
      } else {
        rtphdrlen=setRTPHeader(rtpbuf,icebuf,icehdrlen,(byte)nxm.sys.net.DatagramData.ICE3_PACKET,
                                      null,0,(byte)nxm.sys.net.DatagramData.MAC_PACKET,
                                      ptype,ssrcid,seqnum,timestamp,csrc0id);
      }

      //-------- ADD PAYLOAD (compress, encrpyt, and signing if applicable) ---
      if (compressor!=null) {
        // Compress data if a Deflator is set, size is not too large, and "compression"
        // is successful (does not result in a larger size)
        compressor.setInput(databuf,offset,size);
        compressor.finish();
        if (size<MAXPKTBUF+10) compsize=compressor.deflate(rtpbuf,rtphdrlen,size+10);
        if (compsize>size) System.arraycopy(databuf,offset,rtpbuf,rtphdrlen,size);
        else size=compsize;
        compressor.reset();
        if (cipher!=null) { // since 3.5.4 for Bug 2921
          // Encypt data, place encrypted data in rtpbuf, and use encrypted size
          size=cipher.doFinal(rtpbuf,rtphdrlen,size,databuf,offset); //reuse databut to hold encrypted data
          System.arraycopy(databuf,offset,rtpbuf,rtphdrlen,size); //copy back to rtpbuf, size has been reset in the above line
        }
      } else {
        // Encypt data, place encrypted data in rtpbuf, and use encrypted size
        if (cipher!=null) size=cipher.doFinal(databuf,offset,size,rtpbuf,rtphdrlen); // since 3.5.4 for Bug 2921
        else System.arraycopy(databuf,offset,rtpbuf,rtphdrlen,size);
      }
      if (mac!=null) { // since 3.5.4 for Bug 2921
        mac.update(rtpbuf,0,(size+rtphdrlen)); //sign entire packet, including header with fake signature
        int pos=RTP_HEADER_SIZE; //RTP Header Size
        if (csrc0id!=0) pos+=RTP_CSRC_SIZE;
        if (icebuf!=null) pos+=RTP_XHDR_HEADER_SIZE; //skip main hdr
        pos+=RTP_XHDR_HEADER_SIZE; //skip over MAC Hdr
        mac.doFinal(rtpbuf,pos);
        //if (icebuf!=null) modRTPHeaderForMac(rtpbuf,signature,csrc0id,true); //put in real signature
        //else modRTPHeaderForMac(rtpbuf,signature,csrc0id,false); //put in real signature
      }

      //------ CREATE and SEND PACKET-------------------------
      try {
        if (dsock==null && msock==null) {
          System.err.println("dgnetsend: writeRTPICE Null Socket/buffer");
          return false;
        }
        dpacki = new DatagramPacket(rtpbuf,size+rtphdrlen,addr,port);
        if (multicast) msock.send(dpacki);
        else dsock.send(dpacki);
        if (packetDebug) {
          System.out.println("dgnetsend: Packet size "+(size+rtphdrlen)+"-"+new String(rtpbuf)+":::"+dpacki);
          for (int i=0;i<(size+rtphdrlen);i++) {
            System.out.println("BUF["+i+"]="+rtpbuf[i]);
          }
        }
      } catch (Exception e) {
        System.err.println("dgnetsend writeRTPICE socket: Exception: "+e);
        e.printStackTrace();
      }
      return true;
    } catch (Exception e) {
      System.err.println("dgnetsend: writeRTPICE Exception: "+e);
      e.printStackTrace();
    }
    return false;
  }
  //write methods w/no added RTP header
  /** Calls {@link #write(byte[])} to write out a packet
   *  @param sbuf the buffer as a String
   *  @return whether or not the packet was successfully written out
   */
  public boolean write(String sbuf) {
    boolean ret=false;
    try {
      byte[] buf = sbuf.getBytes();
      if (buf==null) { System.err.println("dgnetsend: Null Input"); return ret; }
      ret=write(buf);
    } catch (Exception e) {
      System.err.println("dgnetsend: write Exception: "+e);
      e.printStackTrace();
    }
    return ret;
  }
  /** Write out packet
   *  @param buf the buffer as an array of bytes
   *  @return whether or not the packet was successfully written out
   */
  public boolean write(byte[] buf) {
    if (buf==null || buf.length<1) {
       System.err.println("dgnetsend: Null data input");
       return false;
    }
    if (!keepopen) {
       System.out.println("dgnetsend: Must use write(host,port,buf) if not using keepopen");
       return false;
    }
    try {
      try {
         if (dsock==null && msock==null) {
           System.err.println("dgnetsend: Null Socket/buffer");
           return false;
         }
         dpacki = new DatagramPacket(buf,buf.length,addr,port);
         if (multicast) msock.send(dpacki);
         else dsock.send(dpacki);
         if (packetDebug) System.out.println("dgnetsend: Packet size "+buf.length+"-"+new String(buf)+":::"+dpacki);
         return true;
      } catch (SocketTimeoutException ste) {
         if (verbose) System.out.println("dgnetsend: SocketTimeOutException" + ste);
      }
    } catch (java.io.InterruptedIOException e) {
      if (verbose) System.out.println("dgnetsend: Interrupted:"+e);
    } catch (java.net.SocketException e) {
      if (verbose) System.out.println("dgnetsend: Socket Closed:"+e);
    } catch (Exception e) {//receive can throw IO, PortUnreachable and IllegalBlockingMode Exceptions
      System.err.println("dgnetsend: write Exception: "+e);
      e.printStackTrace();
    }
    return false;
  }
  /** Write the data to the given destination
   *  @param phost destination address
   *  @param pport destination port
   *  @param sbuf  buffer with data to write
   *  @return      true if successful
   */
  public boolean write(String phost,int pport,String sbuf) {
    boolean ret=false;
    try {
      byte[] buf = sbuf.getBytes();
      ret=write(phost,pport,buf);
    } catch (Exception e) {
      System.err.println("dgnetsend: write Exception: "+e);
      e.printStackTrace();
    }
    return ret;
  }
  /** Write the data to the given destination
   *  @param phost the destination address.
   *  @param pport destination port
   *  @param buf   buffer containing data to send
   *  @return        true if successful
   */
  public boolean write(String phost,int pport,byte[] buf) {
    boolean ret=false;
    if (phost==null) { System.err.println("dgnetsend: No Host for write "); return ret; }
    try {
      boolean tmulticast=false;
      int iprefix = Integer.parseInt(phost.substring(0,3));
      if (iprefix>=224 && iprefix<=239) { tmulticast=true; }
      if (verbose && tmulticast) System.out.println("dgnetsend: Multicast:"+iprefix);

      try {// connect to socket ports
        dpacki = new DatagramPacket(buf,buf.length,InetAddress.getByName(phost),port);
        if (tmulticast) msock.send(dpacki);
        else dsock.send(dpacki);
        ret=true;
      } catch (SocketTimeoutException ste) {
        if (verbose) System.out.println("dgnetsend: SocketTimeOutException" + ste);
      } catch (java.io.InterruptedIOException e) {
        if (verbose) System.out.println("dgnetsend: Interrupted:"+e);
      } catch (java.net.SocketException e) {
        if (verbose) System.out.println("dgnetsend: Socket Closed:"+e);
      } catch (Exception e) {//receive can throw IO, PortUnreachable and IllegalBlockingMode Exceptions
        System.err.println("dgnetsend: write Exception: "+e);
        e.printStackTrace();
      }
    } catch (Exception e) {
       System.err.println("dgnetsend: Problem with stream address:"+phost+":"+pport+" ->"+e);
       e.printStackTrace();
    }
    return ret;
  }
  ////////////////////////////////////////////////////////////////////////////////
  //creation and handling of ice packet header for output
  ////////////////////////////////////////////////////////////////////////////////
  /** Creates and handles ice packet header for output
   *  @param buf     buffer data
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param format  Midas format digraph (e.g. "SB", "SI", etc.)
   *  @param jtime   timecode in Julian time
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param boffset byte offset (not used) ??
   *  @return ICE Packet header length
   */
  public int defICE(byte[] buf,int count,int size,String format,double jtime,double p1,double p2,int boffset) {
    jtime = jtime+5e-7; //copied from Time.java - fromJ1950(double sec)
    double wsec = Math.floor(jtime);
    double fsec = 1e-6*Math.floor(1e6*(jtime-wsec));
    return setICEPacket((byte)0,count,size,(short)0,(short)1,(byte)0,(byte)0,Shell.rep,format,(byte)0,(byte)0,0.0,wsec,fsec,p1,p2);
  }
  /** Creates and handles ice packet header for output
   *  @param buf     buffer data
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param jtime   timecode in Julian time for ICE packet header
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param boffset byte offset (not used) ??
   *  @return ICE Packet header length
   */
  public int defICE(byte[] buf,int count,int size,double jtime,double p1,double p2,int boffset) {
    return defICE(buf,count,size,"SB",jtime,p1,p2,boffset);
  }
  // main setICEPacket
  /** Set ICE Packet
   *  @param type    user-defined key
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param user    user-defined field
   *  @param channel port channel number
   *  @param id1     first subsystem ID
   *  @param id2     second subsystem ID
   *  @param rep     data representation
   *  @param format  Midas format digraph (e.g. "SB", "SI", etc.)
   *  @param mode    timecode mode (0=OFF, 1=CPU, 2=ZTC, 3=SDN, 4=SMS, 5=DTL, 6=IRD, 7=SDDS)
   *  @param status  timecode status (2=OKNC, 1=OK, -1=InActive, -2=BadParam, -3=NoBits, -4=NoBarker,
                                     -5=BCDErr, -6=NAN, -7=NoClock, -8=BadStart, -9=NTPErr)
   *  @param offset  offset at which timecode applies
   *  @param wsec    timecode whole seconds
   *  @param fsec    timecode fractional seconds
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @return ICE Packet header length
   */
  public int setICEPacket(byte type, int count, int size, short user, short channel, byte id1, byte id2,
                            byte rep, String format, byte mode, byte status, double offset, double wsec,
                            double fsec, double p1, double p2) {
     int ilen = setICEPacket(type,count,size,user,channel,id1,id2,rep,format,mode,status,offset,wsec,fsec);
     icePacket.setUserData(p1,0);
     icePacket.setUserData(p2,1);
     return ilen;
  }
  /** Set ICE Packet
   *  @param type    user-defined key
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param user    user-defined field
   *  @param channel port channel number
   *  @param id1     first subsystem ID
   *  @param id2     second subsystem ID
   *  @param rep     data representation
   *  @param format  Midas format digraph (e.g. "SB", "SI", etc.)
   *  @param mode    timecode mode (0=OFF, 1=CPU, 2=ZTC, 3=SDN, 4=SMS, 5=DTL, 6=IRD, 7=SDDS)
   *  @param status  timecode status (2=OKNC, 1=OK, -1=InActive, -2=BadParam, -3=NoBits, -4=NoBarker,
                                     -5=BCDErr, -6=NAN, -7=NoClock, -8=BadStart, -9=NTPErr)
   *  @param offset  offset at which timecode applies
   *  @param wsec    timecode whole seconds
   *  @param fsec    timecode fractional seconds
   *  @return ICE Packet header length
   */
  public int setICEPacket(byte type, int count, int size, short user, short channel, byte id1, byte id2,byte rep, String format,
                            byte mode, byte status, double offset, double wsec,double fsec) {
     icePacket.setUserKey(type); //user defined key
     icePacket.setCount(count); //packet number since start of xfer
     int bpa = Data.getBPA(format); //get Bytes per atom
     int nsize=size;
     if (bpa>0) nsize=nsize/bpa;
     icePacket.setSize(nsize); //data elements in packet
     icePacket.setUser(user); //user defined field
     icePacket.setChannel(channel); //port channel number
     icePacket.setID(id1, id2); //subsystem IDs
     icePacket.setRep(rep); //data rep (Vax,Ieee,Eeei) - usually Shell.rep
     icePacket.setFormat(format); // Midas format digraph, sets bytes per atom (mode*type),mode, and type (MODE=S,C,V.., TYPE=F,D,L,...)
     icePacket.setTC(mode,status,offset,wsec,fsec); // Set timecode mode, status, offset whole seconds and fractional seconds
     return ICEPacket.headerLength;
  }
  //These are not used in TODO E Streams as TODO E has user data sections as doubles only, only floats added here although other available
  /** Creates and handles ice packet header for output
   *  @param buf     buffer data (not used) ??
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param boffset byte offset (not used) ??
   *  @return ICE Packet header length
   */
  public int defICE(byte[] buf,int count,int size,int boffset) {
    return setICEPacket((byte)0,count,size,(short)0,(short)1,(byte)0,(byte)0,Shell.rep,"SB",(byte)0,(byte)0,0.0,0.0,0.0,0.0,1.0);
  }
  /** Creates and handles ice packet header for output
   *  @param buf     buffer data (not used) ??
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param jtime   timecode in Julian time for ICE packet header
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param p3      third data value for ICE packet header
   *  @param p4      fourth data value for ICE packet header
   *  @param boffset byte offset (not used) ??
   *  @return ICE Packet header length
   */
  public int defICE(byte[] buf,int count,int size,double jtime,float p1,float p2,float p3,float p4,int boffset) {
    jtime = jtime+5e-7; //copied from Time.java - fromJ1950(double sec)
    double wsec = Math.floor(jtime);
    double fsec = 1e-6*Math.floor(1e6*(jtime-wsec));
    return setICEPacket((byte)0,count,size,(short)0,(short)1,(byte)0,(byte)0,Shell.rep,"SI",(byte)0,(byte)0,0.0,wsec,fsec,p1,p2,p3,p4);
  }
  /** Set ICE Packet
   *  @param type    user-defined key
   *  @param count   packet number since start of xfer
   *  @param size    data elements in packet
   *  @param user    user-defined field
   *  @param channel port channel number
   *  @param id1     first subsystem ID
   *  @param id2     second subsystem ID
   *  @param rep     data representation
   *  @param format  Midas format digraph (e.g. "SB", "SI", etc.)
   *  @param mode    timecode mode (0=OFF, 1=CPU, 2=ZTC, 3=SDN, 4=SMS, 5=DTL, 6=IRD, 7=SDDS)
   *  @param status  timecode status (2=OKNC, 1=OK, -1=InActive, -2=BadParam, -3=NoBits, -4=NoBarker,
                                     -5=BCDErr, -6=NAN, -7=NoClock, -8=BadStart, -9=NTPErr)
   *  @param offset  offset at which timecode applies
   *  @param wsec    timecode whole seconds
   *  @param fsec    timecode fractional seconds
   *  @param p1      first data value for ICE packet header
   *  @param p2      second data value for ICE packet header
   *  @param p3      third data value for ICE packet header
   *  @param p4      fourth data value for ICE packet header
   *  @return ICE Packet header length
   */
  public int setICEPacket(byte type, int count, int size, short user, short channel, byte id1, byte id2,
                            byte rep, String format, byte mode, byte status, double offset, double wsec,
                            double fsec,float p1,float p2,float p3,float p4) {
     int ilen = setICEPacket(type,count,size,user,channel,id1,id2,rep,format,mode,status,offset,wsec,fsec);
     icePacket.setUserData(p1,0);
     icePacket.setUserData(p2,1);
     icePacket.setUserData(p3,2);
     icePacket.setUserData(p4,3);
     return ilen;
  }

  /**
   * Creation and handling of rtp packet header for output
   * <pre>
   * The RTP header has the following format:
   *  0                   1                   2                   3
   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |V=2|P|X|  CC   |M|     PT      |       sequence number         |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                           timestamp                           |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |           synchronization source (SSRC) identifier            |
   * +=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
   * |            contributing source (CSRC) identifiers             |
   * |                             ....                              |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * The CC in RTPVPXCC tells how many CSRC's (0-15) follow the fixed header
   * If the X bit in the RTP header is one, a variable-length header
   * extension is appended to the RTP header, following the CSRC list if
   * present. The header extension contains a 16-bit length field that
   * counts the number of 32-bit words in the extension, excluding the
   * four-octet extension header (therefore zero is a valid length).
   * P is padding and not used here at all
   *  0                   1                   2                   3
   *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |      defined by profile       |           length              |
   * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   * |                        header extension                       |
   * |                             ....                              |
   *</pre>
   * Should be sending out RTCP with ReceiverReport (RR)
   *
   * method params updated in 3.5.4 for Bug 2921 (i.e. API breakage)
   *
   * @param buf     buffer in which to store RTPHeader
   * @param xhdr    first extended header (or null)
   * @param xlen    length of first extended header
   * @param xtype   MAC_PACKET type or ICE3_PACKET type if mac=null
   * @param xhdr2   second extended header (or null)
   * @param xlen2   length of second extended header
   * @param xtype2  ICE3_PACKET type (or MAC_PACKET type if mac=null)
   * @param ptype   payload type
   * @param ssrcid  source id
   * @param snum    sequence number
   * @param tstamp  timestamp
   * @param csrc0id first contributing source
   *                (only supporting 1, though 15 are allowed)
   * @return        position of next available byte in buffer
   *                (i.e equivalent to number of bytes consumed by header)
   */
  public int setRTPHeader(byte[] buf,byte[] xhdr,int xlen,byte xtype,byte[] xhdr2,int xlen2,byte xtype2,
                          short ptype,int ssrcid,int snum,int tstamp,int csrc0id) {
    //only added 1 csrc although 15 are allowed
    int boffset=0;
    int pos=RTP_HEADER_SIZE;  // the first byte position beyond regular header without CSRC

    // Set the first byte of the RTP Header |V|P|X|CC|
    if (xhdr!=null && xlen>0) buf[boffset]=(byte)0x90; //set V=2 and the X flag
    else buf[boffset]=(byte)0x80; //only set the V=2 flag
    if (csrc0id!=0) {
      buf[boffset]|=0x01; //set cc flag to 1
      pos+=RTP_CSRC_SIZE;   // the first byte position beyond regular header with CSRC
    }

    // Set the second byte of the RTP Header PT (payload type)
    buf[boffset+1]=(byte)ptype;
    buf[boffset+1]&=(byte)0x7F; //zero out mark and use only up to 128 of ptype
    //now do seq number
    netpackI2S(buf,boffset+2,snum);
    //now do timestamp
    netpackL(buf,boffset+4,tstamp); //should be different type
    //now do ssrc - which should be some type of ip based id
    netpackL(buf,boffset+8,ssrcid);
    //now do csrc if available
    if (csrc0id!=0) netpackL(buf,boffset+12,csrc0id);
    // if no extended header return current byte position
    if (xhdr==null || xlen<=0 || xlen>65000) return pos; //65535 is maxpkt - but assume any extension > 65000 is invalid

    //now do x hdr >0
    int xlen4=xlen>>2; // Convert byte length to word length by dividing by 4
    buf[boffset+pos]=0;
    if (xlen2>0) {
       // ??extended header will contain multiple types
       buf[boffset+pos+1]=(byte)nxm.sys.net.DatagramData.COMPOUND_PACKET;
       // size extended header for both packets by
       // adding second packet size in words plus 2 bytes for ....
       xlen4+=((xlen2>>2)+2);
    } else {
      // just original extended header type
      buf[boffset+pos+1]=xtype;
    }
    netpackI2S(buf,boffset+pos+2,xlen4); //length of x hdr in words

    //put in the xhdr data now if dont have a second
    if (xhdr2==null || xlen2<=0 || xlen2>32000) {
      System.arraycopy(xhdr,0,buf,pos+RTP_XHDR_HEADER_SIZE,xlen); //copy data into rtpbuf
      return pos+RTP_XHDR_HEADER_SIZE+xlen; // return byte position after xhdr
    }

    // have second x hdr
    // 0                   1                   2                   3
    // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
    //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //|     0         |COMPOUND_PACKET|           length              |
    //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //|     0         |      xtype    |           length              |
    //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //|                             xhdr                              |
    //|                             ....                              |
    //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //|     0         |      xtype2   |           length              |
    //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    //|                             xhdr2                             |
    //|                             ....                              |
    pos+=RTP_XHDR_HEADER_SIZE; // jump over COMPOUND_PACKET information
    // set first packet type
    buf[boffset+pos]=0;
    buf[boffset+pos+1]=xtype;
    xlen4=xlen>>2;
    netpackI2S(buf,boffset+pos+2,xlen4); // set length of xhdr in words
    System.arraycopy(xhdr,0,buf,pos+4,xlen); //copy data into rtpbuf
    pos+=RTP_XHDR_HEADER_SIZE+xlen;

    // set second packet type
    buf[boffset+pos]=0;
    buf[boffset+pos+1]=xtype2;
    xlen4=xlen2>>2;
    netpackI2S(buf,boffset+pos+2,xlen4); //set length of xhdr2 in words
    System.arraycopy(xhdr2,0,buf,pos+RTP_XHDR_HEADER_SIZE,xlen2); //copy data into rtpbuf
    pos+=RTP_XHDR_HEADER_SIZE+xlen2;

    return pos; //3 hdrs of type/len
  }

  /** Modify RTP Header for Message Authentication Code Algorithm
   *  @since NeXtMidas 3.5.4 for Bug 2921
   *  @param buf       RTP Header
   *  @param signature packet signature
   *  @param csrc0id   contributing source
   *  @param compound  true if it is a compound packet
   */
  public void modRTPHeaderForMac(byte[] buf,byte[] signature,int csrc0id,boolean compound) {
    int pos=RTP_HEADER_SIZE; //RTP Header Size
    if (csrc0id!=0) pos+=RTP_CSRC_SIZE;
    if (compound) pos+=RTP_XHDR_HEADER_SIZE; //skip main hdr
    pos+=RTP_XHDR_HEADER_SIZE; //skip over MAC Hdr
//    System.out.println("Location="+pos);
//    System.out.println("Buf="+buf[pos]+","+buf[pos+19]);
//    System.out.println("Sig="+signature[0]+","+signature[19]);
//    System.out.println("Len="+signature.length);
    System.arraycopy(signature,0,buf,pos,signature.length);
  }

  //These force the output to IEEE format
  /** Pack data ??
   *  @param buf data buffer ??
   *  @param off buffer offset
   *  @param val buffer value
   */
  public static void netpackI2S (byte[] buf, int off, int val) {
    buf[off+1] = (byte)(val&0xFF);
    buf[off+0] = (byte)((val>>8)&0xFF); //ignores highest 2 bytes
  }
  /** Pack data ??
   *  @param buf data buffer ??
   *  @param off buffer offset
   *  @param val buffer value
   */
  public static void netpackL (byte[] buf, int off, int val) {
    buf[off+3] = (byte)(val&0xFF);
    buf[off+2] = (byte)((val>>8)&0xFF);
    buf[off+1] = (byte)((val>>16)&0xFF);
    buf[off+0] = (byte)((val>>24)&0xFF);
  }
}
