From 989101f70ef4acf8431f794ecd016a2ac7031a9b Mon Sep 17 00:00:00 2001 From: brutzman <brutzman@nps.edu> Date: Sat, 22 Jan 2022 14:50:20 -0800 Subject: [PATCH] integrate enumeration handling from PduFactory; new package path edu.nps.moves.dis7.utilities.DisTime; --- src/edu/nps/moves/dis7/utilities/DisTime.java | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 src/edu/nps/moves/dis7/utilities/DisTime.java diff --git a/src/edu/nps/moves/dis7/utilities/DisTime.java b/src/edu/nps/moves/dis7/utilities/DisTime.java new file mode 100644 index 0000000000..8c43c51511 --- /dev/null +++ b/src/edu/nps/moves/dis7/utilities/DisTime.java @@ -0,0 +1,317 @@ +/** + * Copyright (c) 2008-2022, MOVES Institute, Naval Postgraduate School (NPS). All rights reserved. + * This work is provided under a BSD open-source license, see project license.html and license.txt + */ + +package edu.nps.moves.dis7.utilities; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +/** + * <p>DIS time units are a pain to work with. As specified by the IEEE DIS Protocol specification, + * DIS time units are defined in a custom manner and set + * equal to 2^31 - 1 time units per hour. The DIS time is set to the number of time + * units since the start of the hour. Rollover problems can easily occur. The timestamp field in the PDU header is + * four bytes long and is specified to be an unsigned integer value.</p> + * + * <p>Additionally, there are two types of official timestamps in the PDU header: + * <i>absolute time</i> and <i>relative time</i>. Absolute time is used when the host is synchronized to + * <a href="https://en.wikipedia.org/wiki/Coordinated_Universal_Time" target="_blank">Coordinated Universal Time (UTC)</a>, i.e. the host + * is accurately synchronized with UTC via <a href="https://en.wikipedia.org/wiki/Network_Time_Protocol" target="_blank">Network Time Protocol (NTP)</a>. + * The packet timestamps originating from such hosts can be legitimately + * compared to the timestamp of packets received from other hosts, since they all are + * referenced to the same universal time.</p> + * + * <p><b>Absolute timestamps</b> have their least significant bit (LSB) set to 1, and relative timestamps have their + * LSB set to 0. The idea in the DIS specification is to get the current time since the top of the hour, + * divide by 2^31-1, shift left one bit, then set the LSB to either 0 for relative + * timestamps or 1 for absolute timestamps.</p> + * + * <p><b>Relative timestamps</b> are used when the host does NOT have access to NTP, and hence + * the system time might not be coordinated with that of other hosts. This means that + * a host receiving DIS packets from several hosts might have to set up a per-host + * table to correlate baseline time references before ordering packets, and that + * the PDU timestamp fields from one host is not + * directly comparable to the PDU timestamp field from another host.</p> + * + * <p>The nature of shared DIS data is such that the timestamp values <i>roll over</i> once an + * hour, and simulations must be prepared for that eventuality. In other words, at the top of the hour + * outgoing PDUs will have a timestamp of 1, then just before the end of the hour the + * PDUs will have a timestamp of 2^31 - 1, and then they will roll back over to a value of 1. + * Receiving applications should expect this behavior, and not simply expect a + * monotonically increasing timestamp field.</p> + * + * <p><b>Unix time</b>. Note that many applications in the wild have been known to completely ignore + * the standard and to simply put commonly used <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> (seconds since 1 January 1970) into the + * field. </p> + * + * <p><b>Year time</b>. The rollover associated with official DIS timestamps don't work all that well in numerous applications, + * which often expect a monotonically increasing timestamp field. Such unpredictable rollover variations are also incompatible + * with archival recording or streaming playback of Live-Virtual-Constructive (LVC) behavior streams. + * To avoid such problems, NPS created a "yearly" timestamp which measures + * hundredths of a second since the start of the current year. The maximum value for + * such measurements is 3,153,600,000, which can fit into an unsigned int. + * One hundredth of a second resolution is accurate enough for most applications, and you typically don't have to worry about + * rollover, instead getting only a monotonically increasing timestamp value.</p> + * + * <p><b>TODO: time 0.0</b>. Functionality is needed to define a shared common time origin, and also to + * precisely adjust stream timestamps when coordinating recorded PDU playback within LVC applications. + * We think the ability to "start at time 0.0", or normalizing initial time to zero + * for a recorded PDU stream, is actually a pretty common use case.</p> + * <p> Don McGregor, Mike Bailey, and Don Brutzman</p> + * + * @author DMcG + */ +// * problems unlikely: +// * <p><b>TODO: confirm thread safe</b>. Be careful with the shared instance of this class -- +// * it has static synchronized methods but is not yet +// * confirmed to be thread safe. If you are using multiple threads, suggest you +// * create a new instance of the class for each thread to prevent the values from +// * getting stomped on.</p> + +public class DisTime +{ + + private static Method getTimeMethod; + private final static DisTime disTime = new DisTime(); + + /** Supported timestamp styles and utility methods. + * @see edu.nps.moves.dis7.utilities.PduFactory + */ + public enum TimestampStyle { + /** Clock ticks since top of hour, host synchronized to UTC via Network Time Protocol (NTP) */ + IEEE_ABSOLUTE, + /** Clock ticks since top of hour, host not synchronized to UTC via Network Time Protocol (NTP) */ + IEEE_RELATIVE, + /** Unix time (seconds since 1 January 1970) */ + UNIX, + /** hundreds of a second since the start of the year */ + YEAR + }; + + /** We can marshal the PDU with a timestamp set to any of several styles. + * Remember, you MUST set a timestamp. DIS will regard multiple packets sent + * with the same timestamp as duplicates and may discard them. + * Default value is TimestampStyle.IEEE_ABSOLUTE. + */ + private static TimestampStyle timestampStyle = TimestampStyle.IEEE_ABSOLUTE; + + /** mask for absolute timestamps */ + public static final int ABSOLUTE_TIMESTAMP_MASK = 0x00000001; + + /** mask for relative timestamps */ + public static final int RELATIVE_TIMESTAMP_MASK = 0xfffffffe; + + /** calendar instance */ + private static GregorianCalendar calendar; + + // public static DisTime disTime = null; + + /** + * Shared instance. This is not thread-safe. If you are working in multiple threads, + * create a new instance for each thread. + * return singleton instance of DisTime + */ + /* public static DisTime createInstance() + { + if (disTime == null) { + disTime = new DisTime(); + } + + return disTime; + } + */ + + public DisTime() + { + calendar = new GregorianCalendar(); + } + + /** + * Returns the number of DIS time units since the top of the hour. there are 2^31-1 DIS time + * units per hour. + * @return integer DIS time units since the start of the hour. + */ + private static synchronized int getCurrentDisTimeUnitsSinceTopOfHour() + { + // set calendar object to current time + long currentTime = System.currentTimeMillis(); // UTC milliseconds since 1970 + calendar.setTimeInMillis(currentTime); + + // Set calendar to top of the hour, then compute what the calendar object says was milliseconds since 1970 + // at the top of the hour + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long topOfHour = calendar.getTimeInMillis(); + + // Milliseconds since the top of the hour + long diff = currentTime - topOfHour; + + // It turns out that Integer.MAX_VALUE is 2^31-1, which is the time unit value, ie there are + // 2^31-1 DIS time units in an hour. 3600 sec/hr X 1000 msec/sec divided into the number of + // msec since the start of the hour gives the percentage of DIS time units in the hour, times + // the number of DIS time units per hour, equals the time value + double val = (diff / (3600.0 * 1000.0)) * Integer.MAX_VALUE; + int ts = (int) val; + + return ts; + } + + /** + * Checks local system clock and returns the current DIS standard absolute timestamp, assuming that this host is synchronized to NTP. + * // Fix to bitshift by mvormelch. + * @see <a href="https://en.wikipedia.org/wiki/Network_Time_Protocol" target="_blank">Wikipedia: Network Time Protocol (NTP)</a> + * @return DIS time units, get absolute timestamp + */ + public static synchronized int getCurrentDisAbsoluteTimestamp() + { + int val = getCurrentDisTimeUnitsSinceTopOfHour(); + val = (val << 1) | ABSOLUTE_TIMESTAMP_MASK; // always flip the lsb to 1 + return val; + } + + /** + * Checks local system clock and returns the current DIS standard relative timestamp, which should be used if this host + * is not slaved to NTP. Fix to bitshift by mvormelch + * @see <a href="https://en.wikipedia.org/wiki/Network_Time_Protocol" target="_blank">Wikipedia: Network Time Protocol (NTP)</a> + * @return DIS time units, relative + */ + public static int getCurrentDisRelativeTimestamp() + { + int val = getCurrentDisTimeUnitsSinceTopOfHour(); + val = (val << 1) & RELATIVE_TIMESTAMP_MASK; // always flip the lsb to 0 + return val; + } + + /** + * Checks local system clock and returns a useful timestamp, hundredths of a second since the start of the year. + * This effectively eliminates the need for receivers to handle timestamp rollover, + * as long as you're not working late on New Year's Eve. + * (Previously referred to as NPS timestamp.) + * TODO consider renaming as Annual timestamp. + * TODO consult with DIS working group about timestamp disambiguation. + * @return a timestamp in hundredths of a second since the start of the year + */ + public static synchronized int getCurrentYearTimestamp() + { + // set calendar object to current time + long currentTime = System.currentTimeMillis(); // UTC milliseconds since 1970 + calendar.setTimeInMillis(currentTime); + + // Set calendar to the start of the year + calendar.set(Calendar.MONTH, 0); + calendar.set(Calendar.DAY_OF_MONTH, 1); + calendar.set(Calendar.HOUR, 0); + calendar.set(Calendar.MINUTE, 0); + calendar.set(Calendar.SECOND, 0); + calendar.set(Calendar.MILLISECOND, 0); + long startOfYear = calendar.getTimeInMillis(); + + // Milliseconds since the top of the hour + long diff = currentTime - startOfYear; + diff /= 10; // milliseconds to hundredths of a second + + return (int) diff; + } + + /** + * Checks local system clock and returns a useful timestamp as local Unix time. + * Another option for marshalling with the timestamp field set automatically. The UNIX + * time is conventionally seconds since January 1, 1970. UTC time is used, and leap seconds + * are excluded. This approach is popular in the wild, but the time resolution is not very + * good for high frequency updates, such as aircraft. An entity updating at 30 PDUs/second + * would see 30 PDUs sent out with the same timestamp, and have 29 of them discarded as + * duplicate packets. + * + * Note that there are other "Unix times", such milliseconds since 1/1/1970, saved in a long. + * This cannot be used, since the value is saved in a long. Java's System.getCurrentTimeMillis() + * uses this value. + * + * Unix time (in seconds) rolls over in 2038. + * + * Consult the Wikipedia page on <a href="https://en.wikipedia.org/wiki/Unix_time">Unix time</a> for the gory details + * @return seconds since 1970 + */ + public static synchronized int getCurrentUnixTimestamp() + { + long t = System.currentTimeMillis(); + t /= 1000l; // NB: integer division, convert milliseconds to seconds + return (int) t; + } + + /** + * Convert timestamp value to string for logging and diagnostics. + * TODO consider different formats for different timestampStyle values. + * @param timestamp value in milliseconds + * @see GregorianCalendar + * @return string value provided by GregorianCalendar + */ + public static String convertToString(int timestamp) + { + GregorianCalendar newCalendar = new GregorianCalendar(); + newCalendar.setTimeInMillis(timestamp); + DateFormat formatter = new SimpleDateFormat("HH:mm:ss"); + return formatter.format(newCalendar.getTime()); + } + + /** Set one of four time references as timestampStyle: IEEE_ABSOLUTE, IEEE_RELATIVE, UNIX, or YEAR. + * @param newtTimestampStyle the timestamp style to set for this PDU + */ + public static void setTimestampStyle(TimestampStyle newtTimestampStyle) { + timestampStyle = newtTimestampStyle; + setTimestampMethod(); + } + + public static void setTimestampMethod() + { + try { + switch (timestampStyle) + { + case IEEE_ABSOLUTE: + getTimeMethod = DisTime.class.getDeclaredMethod("getCurrentDisAbsoluteTimestamp", new Class<?>[0]); + break; + + case IEEE_RELATIVE: + getTimeMethod = DisTime.class.getDeclaredMethod("getCurrentDisRelativeTimestamp", new Class<?>[0]); + break; + + case UNIX: + getTimeMethod = DisTime.class.getDeclaredMethod("getCurrentUnixTimestamp", new Class<?>[0]); + break; + + case YEAR: // formerly NPS: + getTimeMethod = DisTime.class.getDeclaredMethod("getCurrentYearTimestamp", new Class<?>[0]); + break; + + default: + getTimeMethod = DisTime.class.getDeclaredMethod("getCurrentDisAbsoluteTimestamp", new Class<?>[0]); + break; + } + } catch (NoSuchMethodException ex) { + throw new RuntimeException(ex); + } + } + + /** Retrieve the current timestamp in the time stamp style set at factory + * instantiation. + * @return the current timestamp in the time stamp style set at factory + * instantiation + */ + public static int getTimestamp() + { + try { + if (getTimeMethod == null) + setTimestampMethod(); // avoid NPE + return (int) getTimeMethod.invoke(disTime, (Object[]) null); + } + catch (IllegalAccessException | InvocationTargetException ex) { + throw new RuntimeException(ex); + } + } + +} -- GitLab