/*
* -----------------------------------------------------------------------
* Copyright © 2013-2015 Meno Hochschild, <http://www.menodata.de/>
* -----------------------------------------------------------------------
* This file (SntpMessage.java) is part of project Time4J.
*
* Time4J is free software: You can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 2.1 of the License, or
* (at your option) any later version.
*
* Time4J is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Time4J. If not, see <http://www.gnu.org/licenses/>.
* -----------------------------------------------------------------------
*/
package net.time4j.clock;
import net.time4j.Moment;
import net.time4j.SystemClock;
import net.time4j.scale.TimeScale;
import java.io.IOException;
/**
* <p>Message for the SNTP-protocol (RFC 4330). </p>
*
* <p>NTP-timestamps will be calculated at best in microsecond precision. </p>
*
* @author Meno Hochschild
* @since 2.1
*/
/*[deutsch]
* <p>Nachricht für das SNTP-Protokoll (RFC 4330). </p>
*
* <p>NTP-Zeitstempel werden maximal in Mikrosekundengenauigkeit berechnet. </p>
*
* @author Meno Hochschild
* @since 2.1
*/
public final class SntpMessage {
//~ Statische Felder/Initialisierungen --------------------------------
// Mikrosekunden seit [1900-01-01T00:00:00Z] (relativ zu 1.1.1970)
private static final long OFFSET_1900 = 2208988800000000L;
// Mikrosekunden seit [2036-02-07T06:28:16Z] (ohne Schaltsekunden!)
private static final long OFFSET_2036 = -2085978496000000L;
private static final int MIO = 1000000;
private static final double MIO_AS_DOUBLE = 1000000.0;
private static final byte[] NULL_REF_ID = new byte[] {0, 0, 0, 0};
private static final double BIT08 = 256.0;
private static final double BIT16 = 65536.0;
//~ Instanzvariablen --------------------------------------------------
private final byte leapIndicator;
private final byte version;
private final byte mode;
private final short stratum;
private final short pollInterval;
private final byte precision;
private final double rootDelay;
private final double rootDispersion;
private final byte[] refID;
private final double referenceTimestamp;
private final double originateTimestamp;
private final double receiveTimestamp;
private final double transmitTimestamp;
//~ Konstruktoren -----------------------------------------------------
/**
* <p>Konstruiert eine Anfrage des Clients. </p>
*
* @param version4 NTP4-Version statt NTP3-Version verwenden?
*/
SntpMessage(boolean version4) {
super();
this.leapIndicator = 0;
this.version = (byte) (version4 ? 4 : 3);
this.mode = 3;
this.stratum = 0;
this.pollInterval = 0;
this.precision = 0;
this.rootDelay = 0;
this.rootDispersion = 0;
this.refID = NULL_REF_ID;
this.referenceTimestamp = 0;
this.originateTimestamp = 0;
this.receiveTimestamp = 0;
this.transmitTimestamp = getLocalTimestamp();
}
/**
* <p>Konstruiert eine Antwort des Servers. </p>
*
* @param data Antwort-Daten des Servers
* @param expectedOriginateTS erwarteter Zeitstempel im NTP-Format
* @param expectedVersion erwartete NTP-Version des Servers
* @throws IOException bei Plausibilitätsverletzungen
*/
SntpMessage(
byte[] data,
double expectedOriginateTS,
byte expectedVersion
) throws IOException {
super();
this.leapIndicator = (byte) ((data[0] >> 6) & 0x3);
this.version = (byte) ((data[0] >> 3) & 0x7);
this.mode = (byte) (data[0] & 0x7);
this.stratum = toUnsigned(data[1]);
this.pollInterval = toUnsigned(data[2]);
this.precision = data[3];
this.rootDelay = (
(data[4] * BIT08)
+ toUnsigned(data[5])
+ (toUnsigned(data[6]) / BIT08)
+ (toUnsigned(data[7]) / BIT16)
);
int dispersion = 0;
for (int i = 0; i < 4; i++) {
int unsigned = (data[i + 8] & 0xFF);
dispersion |= (unsigned << (24 - i * 8));
}
this.rootDispersion = (dispersion / BIT16);
byte[] r = new byte[4];
r[0] = data[12];
r[1] = data[13];
r[2] = data[14];
r[3] = data[15];
this.refID = r;
this.referenceTimestamp = decode(data, 16);
this.originateTimestamp = decode(data, 24);
this.receiveTimestamp = decode(data, 32);
this.transmitTimestamp = decode(data, 40);
// Plausibilitätsprüfungen
if (this.transmitTimestamp == 0) {
throw new IOException(
"Server hasn't sent any transmit timestamp.");
} else if (
Math.abs(expectedOriginateTS - this.originateTimestamp) >= 0.001
&& (this.mode == 4)
) {
throw new IOException(
"Originate timestamp does not match sent timestamp: "
+ this.originateTimestamp
+ " (expected = "
+ expectedOriginateTS
+ ")"
);
} else if (
(this.mode != 4)
&& (this.mode != 5)
) {
throw new IOException(
"Unexpected server mode: " + this.mode);
} else if (
(this.leapIndicator < 0)
|| (this.leapIndicator > 3)
) {
throw new IOException(
"Unexpected leap indicator: " + this.leapIndicator);
} else if (
(this.version < 1)
|| (this.version > 4)
|| ((this.mode == 4) && (this.version != expectedVersion))
) {
throw new IOException(
"Unexpected ntp version: " + this.version);
} else if (
(this.stratum < 0)
|| (this.stratum > 15)
) {
throw new IOException(
"Unexpected stratum: " + this.stratum);
}
}
//~ Methoden ----------------------------------------------------------
/**
* <p>Yields the LI-bits as appointment of a coming leap second. </p>
*
* <ul><li>0 - no warning. </li>
* <li>1 - last minute of day has 61 seconds. </li>
* <li>2 - last minute of day has 59 seconds. </li>
* <li>3 - alert state (missing clock synchronization). </li></ul>
*
* @return byte
* @since 2.1
*/
/*[deutsch]
* <p>Ermittelt die LI-Bits als Ankündigung einer bevorstehenden
* Schaltsekunde. </p>
*
* <ul><li>0 - Keine Warnung. </li>
* <li>1 - Letzte Minute des Tages hat 61 Sekunden. </li>
* <li>2 - Letzte Minute des Tages hat 59 Sekunden. </li>
* <li>3 - Alarmzustand (fehlende Uhrzeitsynchronisation). </li></ul>
*
* @return byte
* @since 2.1
*/
public byte getLeapIndicator() {
return this.leapIndicator;
}
/**
* <p>Displays the NTP-version. </p>
*
* @return 3 (if only IPv4 is supported) else 4
* @since 2.1
*/
/*[deutsch]
* <p>Zeigt die NTP-Version an. </p>
*
* @return 3 (wenn nur IPv4 unterstützt wird), sonst 4
* @since 2.1
*/
public byte getVersion() {
return this.version;
}
/**
* <p>Displays the mode. </p>
*
* @return 3 (client-mode) or 4 (server-mode) oder 5 (broadcast)
* @since 2.1
*/
/*[deutsch]
* <p>Zeigt den Modus an. </p>
*
* @return 3 (Client-Modus) oder 4 (Server-Modus) oder 5 (Broadcast)
* @since 2.1
*/
public byte getMode() {
return this.mode;
}
/**
* <p>Displays the stratum-value indicating the <i>distance</i> of
* the original time source. </p>
*
* <p>Usually it is the count of involved clock servers respective
* layers. The value {@code 0} indicates a <i>kiss-o'-death</i>-message
* by which the server signals to the client that repeated requests should
* be immediately stopped. </p>
*
* @return 0 (unspecified) oder 1 (direkt) oder 2-15 (sekundär)
* @since 2.1
*/
/*[deutsch]
* <p>Zeigt den Stratum-Wert als Maß für die
* <i>Entfernung</i> der Zeitquelle an. </p>
*
* <p>In der Regel handelt es sich um die Anzahl der beteiligten
* Uhrzeit-Server bzw. Schichten. Der Wert {@code 0} zeigt eine
* <i>kiss-o'-death</i>-Nachricht an, womit der Server dem
* Client signalisiert, das wiederholte Senden von Anfragen sofort
* einzustellen. </p>
*
* @return 0 (unspezifiziert) oder 1 (direkt) oder 2-15 (sekundär)
* @since 2.1
*/
public short getStratum() {
return this.stratum;
}
/**
* <p>Yields the maximum interval between two successful server messages
* in seconds as exponent for base 2. </p>
*
* <p>This property is only relevant if the server is sending messages
* in the <i>broadcast</i>-mode. In all other cases the server should
* send the value{@code 0}. </p>
*
* @return broadcast-mode: value in range {@code 4 <= pollInterval <= 17}
* @since 2.1
*/
/*[deutsch]
* <p>Liefert das maximale Intervall zwischen zwei erfolgreichen
* Server-Nachrichten in Sekunden als Exponent zur Basis 2. </p>
*
* <p>Diese Eigenschaft ist nur von Belang, wenn der Server Nachrichten
* im <i>broadcast</i>-Modus sendet. Sonst sollte der Server den Wert
* {@code 0} senden. </p>
*
* @return broadcast-Modus: Wert im Bereich {@code 4 <= pollInterval <= 17}
* @since 2.1
*/
public int getPollInterval() {
return this.pollInterval;
}
/**
* <p>Yields the precision of the server clock in seconds as exponent
* for the base 2. </p>
*
* @return int in the usual range {@code -6 <= precision <= -23}
* @since 2.1
*/
/*[deutsch]
* <p>Liefert die Genauigkeit der Systemuhr auf dem Server in Sekunden
* als Exponent zur Basis 2. </p>
*
* @return int im üblichen Bereich {@code -6 <= precision <= -23}
* @since 2.1
*/
public int getPrecision() {
return this.precision;
}
/**
* <p>Yields the total delay in seconds relative to the primary source. </p>
*
* <p>This information only concerns the NTP-server, not the actual
* network traffic with the client. </p>
*
* @return round-trip-delay in seconds
* @since 2.1
*/
/*[deutsch]
* <p>Liefert die gesamte Verzögerung in Sekunden relativ zur
* primären Referenzquelle. </p>
*
* <p>Es handelt sich um eine Information, die nur den NTP-Server betrifft,
* nicht aber den aktuellen Netzverkehr mit dem Client. </p>
*
* @return round-trip-delay in Sekunden
* @since 2.1
*/
public double getRootDelay() {
return this.rootDelay;
}
/**
* <p>Yields the maximum error in seconds relative to the primary
* source. </p>
*
* <p>This information only concerns the NTP-server, not the actual
* network traffic with the client. </p>
*
* @return maximum error in seconds
* @since 2.1
*/
/*[deutsch]
* <p>Liefert den maximalen Fehler in Sekunden relativ zur
* primären Referenzquelle. </p>
*
* <p>Es handelt sich um eine Information, die nur den NTP-Server betrifft,
* nicht aber den aktuellen Netzverkehr mit dem Client. </p>
*
* @return maximaler Fehler in Sekunden
* @since 2.1
*/
public double getRootDispersion() {
return this.rootDispersion;
}
/**
* <p>Identifies a reference source. </p>
*
* <p>If the connected server is a primary NTP-server (stratum = 1) then
* the return value will usually be a string from following list: </p>
*
* <ul>
* <li>LOCL - uncalibrated local clock</li>
* <li>CESM - calibrated Cesium clock</li>
* <li>RBDM - calibrated Rubidium clock</li>
* <li>PPS - calibrated quartz clock or other pulse-per-second source</li>
* <li>IRIG - Inter-Range Instrumentation Group</li>
* <li>ACTS - NIST telephone modem service</li>
* <li>USNO - USNO telephone modem service</li>
* <li>PTB - PTB (Germany) telephone modem service</li>
* <li>TDF - Allouis (France) Radio 164 kHz</li>
* <li>DCF - Mainflingen (Germany) Radio 77.5 kHz</li>
* <li>MSF - Rugby (UK) Radio 60 kHz</li>
* <li>WWV - Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz</li>
* <li>WWVB - Boulder (US) Radio 60 kHz</li>
* <li>WWVH - Kauai Hawaii (US) Radio 2.5, 5, 10, 15 MHz</li>
* <li>CHU - Ottawa (Canada) Radio 3330, 7335, 14670 kHz</li>
* <li>LORC - LORAN-C radionavigation getChronology</li>
* <li>OMEG - OMEGA radionavigation getChronology</li>
* <li>GPS - Global positioning getChronology</li>
* </ul>
*
* <p>A secondary IPv4-server will return a 32-bit-IP-address else the
* return value represents the first 32 bits of a MD5-hash value of a
* IPv6- or NSAP-address of the synchronization source. </p>
*
* <p>In case of a ({@code stratum == 0}) - reply of the server the
* return value describes a kiss-or-death-message. The client should
* then not repeat the request (or at least not within the next minute).
* A non-exhausting selection: </p>
*
* <ul>
* <li>ACST - Association belongs to an anycast server</li>
* <li>AUTH - Server authentication failed</li>
* <li>AUTO - Autokey sequence failed</li>
* <li>BCST - Association belongs to a broadcast server</li>
* <li>CRYP - Cryptographic authentication or identification failed</li>
* <li>DENY - Access denied by remote server</li>
* <li>DROP - Lost peer in symmetric mode</li>
* <li>RSTR - Access denied due to local policy</li>
* <li>INIT - Association has not yet synchronized for the first time</li>
* <li>MCST - Association belongs to a manycast server</li>
* <li>NKEY - No key found. Either the key was never installed or is not
* trusted</li>
* <li>RATE - Rate exceeded. The server has temporarily denied access
* because the client exceeded the rate threshold</li>
* <li>RMOT - Somebody is tinkering with the association from a remote
* host running ntpdc. Not to worry unless some rascal has stolen your
* keys</li>
* <li>STEP - A step change in getChronology time has occurred, but the
* association has not yet resynchronized</li>
* </ul>
*
* @return String
* @since 2.1
*/
/*[deutsch]
* <p>Identifiziert eine Referenzquelle. </p>
*
* <p>Wenn es sich um einen primären NTP-Server handelt (stratum = 1),
* dann wird der Rückgabewert in der Regel ein String aus der folgenden
* Liste sein: </p>
*
* <ul>
* <li>LOCL - uncalibrated local clock</li>
* <li>CESM - calibrated Cesium clock</li>
* <li>RBDM - calibrated Rubidium clock</li>
* <li>PPS - calibrated quartz clock or other pulse-per-second source</li>
* <li>IRIG - Inter-Range Instrumentation Group</li>
* <li>ACTS - NIST telephone modem service</li>
* <li>USNO - USNO telephone modem service</li>
* <li>PTB - PTB (Germany) telephone modem service</li>
* <li>TDF - Allouis (France) Radio 164 kHz</li>
* <li>DCF - Mainflingen (Germany) Radio 77.5 kHz</li>
* <li>MSF - Rugby (UK) Radio 60 kHz</li>
* <li>WWV - Ft. Collins (US) Radio 2.5, 5, 10, 15, 20 MHz</li>
* <li>WWVB - Boulder (US) Radio 60 kHz</li>
* <li>WWVH - Kauai Hawaii (US) Radio 2.5, 5, 10, 15 MHz</li>
* <li>CHU - Ottawa (Canada) Radio 3330, 7335, 14670 kHz</li>
* <li>LORC - LORAN-C radionavigation getChronology</li>
* <li>OMEG - OMEGA radionavigation getChronology</li>
* <li>GPS - Global positioning getChronology</li>
* </ul>
*
* <p>Ein sekundärer IPv4-Server wird hier eine 32-Bit-IP-Adresse
* liefern, sonst handelt es sich um die ersten 32 Bits eines MD5-Haschwerts
* einer IPv6- oder NSAP-Adresse der Synchronisationsquelle. </p>
*
* <p>Im Fall einer ({@code stratum == 0}) - Antwort des Serves beschreibt
* der Rückgabewert eine kiss-or-death-Nachricht. Der Client sollte
* dann nicht die Anfrage wiederholen (oder wenigstens nicht innerhalb der
* nächsten Minute). Eine nicht erschöpfende Auswahl: </p>
*
* <ul>
* <li>ACST - Association belongs to an anycast server</li>
* <li>AUTH - Server authentication failed</li>
* <li>AUTO - Autokey sequence failed</li>
* <li>BCST - Association belongs to a broadcast server</li>
* <li>CRYP - Cryptographic authentication or identification failed</li>
* <li>DENY - Access denied by remote server</li>
* <li>DROP - Lost peer in symmetric mode</li>
* <li>RSTR - Access denied due to local policy</li>
* <li>INIT - Association has not yet synchronized for the first time</li>
* <li>MCST - Association belongs to a manycast server</li>
* <li>NKEY - No key found. Either the key was never installed or is not
* trusted</li>
* <li>RATE - Rate exceeded. The server has temporarily denied access
* because the client exceeded the rate threshold</li>
* <li>RMOT - Somebody is tinkering with the association from a remote
* host running ntpdc. Not to worry unless some rascal has stolen your
* keys</li>
* <li>STEP - A step change in getChronology time has occurred, but the
* association has not yet resynchronized</li>
* </ul>
*
* @return String
* @since 2.1
*/
public String getReferenceIdentifier() {
return this.getRefIDAsString();
}
/**
* <p>NTP-timestamp when the time of the server was set or corrected
* last time. </p>
*
* <p>The method yields {@code 0} if no request has been sent yet. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
/*[deutsch]
* <p>NTP-Timestamp wann die Zeit auf dem Server zuletzt gesetzt oder
* korrigiert wurde. </p>
*
* <p>Die Methode liefert {@code 0}, wenn noch nichts gesendet worden
* ist. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
public double getReferenceTimestamp() {
return this.referenceTimestamp;
}
/**
* <p>NTP-timestamp when the client request was sent. </p>
*
* <p>The method yields {@code 0} if no request has been sent yet. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
/*[deutsch]
* <p>NTP-Timestamp wann die Zeitabfrage vom Client gesendet wurde. </p>
*
* <p>Die Methode liefert {@code 0}, wenn noch nichts gesendet worden
* ist. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
public double getOriginateTimestamp() {
return this.originateTimestamp;
}
/**
* <p>NTP-timestamp when the server received the client request. </p>
*
* <p>The method yields {@code 0} if no request has been sent yet. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
/*[deutsch]
* <p>NTP-Timestamp wann die Zeitabfrage vom Server empfangen wurde. </p>
*
* <p>Die Methode liefert {@code 0}, wenn noch nichts gesendet worden
* ist. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
public double getReceiveTimestamp() {
return this.receiveTimestamp;
}
/**
* <p>NTP-timestamp when the client or server request was sent. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
/*[deutsch]
* <p>NTP-Timestamp wann die Abfrage bzw. Antwort vom Client bzw. vom
* Server gesendet wurde. </p>
*
* @return NTP-time in seconds since 1900-01-01
* @since 2.1
*/
public double getTransmitTimestamp() {
return this.transmitTimestamp;
}
/**
* <p>Returns a human-readable form of the internal state. </p>
*
* @return String
*/
/*[deutsch]
* <p>Gibt den internen Zustand in menschenlesbarer Form aus. </p>
*
* @return String
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder(300);
sb.append(this.getClass().getName());
sb.append("[version=");
sb.append(this.version);
sb.append(", mode=");
switch (this.mode) {
case 3:
sb.append("client");
break;
case 4:
sb.append("server");
break;
case 5:
sb.append("broadcast");
break;
default:
sb.append(this.mode);
}
sb.append(", leap-indicator=");
sb.append(this.leapIndicator);
sb.append(", stratum=");
sb.append(this.stratum);
sb.append(", poll-interval=");
sb.append(this.pollInterval);
sb.append(", precision=");
sb.append(this.precision);
sb.append(", root-delay=");
sb.append(this.rootDelay);
sb.append(", root-dispersion=");
sb.append(this.rootDispersion);
sb.append(", reference-identifier=");
sb.append(this.getRefIDAsString());
sb.append(", reference-timestamp=");
sb.append(toString(this.referenceTimestamp));
sb.append(", originate-timestamp=");
sb.append(toString(this.originateTimestamp));
sb.append(", receive-timestamp=");
sb.append(toString(this.receiveTimestamp));
sb.append(", transmit-timestamp=");
sb.append(toString(this.transmitTimestamp));
sb.append(']');
return sb.toString();
}
/**
* <p>Converts given NTP-timestamp to a microsecond value relative to
* the UNIX- epoch. </p>
*
* @param ntpTimestamp NTP-timestamp (seconds since 1900-01-01)
* @return long-Wert relative to 1970-01-01 in microseconds without
* leap seconds
* @since 2.1
*/
/*[deutsch]
* <p>Wandelt den NTP-Timestamp in einen Mikrosekundenwert relativ
* zur UNIX-Epoche um. </p>
*
* @param ntpTimestamp NTP-Timestamp (Sekunden seit 1.1.1900)
* @return long-Wert relativ zum 1. Januar 1970 (Mikrosekundenwert
* ohne Zählung von UTC-Schaltsekunden)
* @since 2.1
*/
public static long convert(double ntpTimestamp) {
return (long) ((ntpTimestamp * MIO) - OFFSET_1900);
}
/**
* <p>Liefert die rohen Bytes dieser {@code SntpMessage} zum Versenden
* an den NTP-Server. </p>
*
* @return byte-Array
*/
byte[] getBytes() {
byte[] ret = new byte[48];
ret[0] =
(byte) (
(this.leapIndicator << 6)
| (this.version << 3)
| this.mode
);
// wird nie ausgewertet, da nur auf die Client-Message angewandt
if (this.mode != 3) {
ret[1] = (byte) this.stratum;
ret[2] = (byte) this.pollInterval;
ret[3] = this.precision;
int rdelay = (int) (this.rootDelay * BIT16);
ret[4] = (byte) ((rdelay >> 24) & 0xFF);
ret[5] = (byte) ((rdelay >> 16) & 0xFF);
ret[6] = (byte) ((rdelay >> 8) & 0xFF);
ret[7] = (byte) (rdelay & 0xFF);
long rdisp = (long) (this.rootDispersion * BIT16);
ret[8] = (byte) ((rdisp >> 24) & 0xFF);
ret[9] = (byte) ((rdisp >> 16) & 0xFF);
ret[10] = (byte) ((rdisp >> 8) & 0xFF);
ret[11] = (byte) (rdisp & 0xFF);
for (int i = 0; i < 4; i++) {
ret[12 + i] = this.refID[i];
}
encode(ret, 16, this.referenceTimestamp);
encode(ret, 24, this.originateTimestamp);
encode(ret, 32, this.receiveTimestamp);
}
encode(ret, 40, this.transmitTimestamp);
return ret;
}
/**
* <p>Liefert den aktuellen lokalen NTP-Timestamp. </p>
*
* @return aktuelle Zeit im NTP-Format (Sekunden seit 1.1.1900)
*/
static double getLocalTimestamp() {
long ut1 = SystemClock.MONOTONIC.currentTimeInMicros();
return ((ut1 + OFFSET_1900) / MIO_AS_DOUBLE);
}
private String getRefIDAsString() {
StringBuilder sb = new StringBuilder();
if (
(this.stratum == 0)
|| (this.stratum == 1)
) {
for (int i = 0; i < 4; i++) {
char c = (char) this.refID[i];
if (c == 0) {
break;
} else {
sb.append(c);
}
}
} else if (this.version == 3) {
sb.append(toUnsigned(this.refID[0]));
sb.append('.');
sb.append(toUnsigned(this.refID[1]));
sb.append('.');
sb.append(toUnsigned(this.refID[2]));
sb.append('.');
sb.append(toUnsigned(this.refID[3]));
} else if (this.version == 4) {
int ref = 0;
for (int i = 0; i < 4; i++) {
int unsigned = (this.refID[i] & 0xFF);
ref |= (unsigned << (24 - i * 8));
}
return Integer.toHexString(ref);
} else {
sb.append('?');
}
return sb.toString();
}
private static String toString(double ntpTimestamp) {
long micros = convert(ntpTimestamp);
Moment m =
Moment.of(
micros / MIO,
(int) ((micros % MIO) * 1000),
TimeScale.POSIX);
return m.toString();
}
// NTP-Timestamp aus byte-Array dekodieren
private static double decode(
byte[] data,
int pointer
) {
long ntp = 0L;
for (int i = 0; i < 8; i++) {
long unsigned = (data[i + pointer] & 0xFF);
ntp |= (unsigned << (56 - i * 8));
}
// Festkomma vor Bit 32, deshalb Bits nach rechts schieben
long integer = ((ntp >>> 32) & 0xFFFFFFFFL);
long fraction = (((ntp & 0xFFFFFFFFL) * MIO) >>> 32);
long off = (((integer & 0x80000000L) == 0) ? OFFSET_2036 : OFFSET_1900);
long ut1 = (integer * MIO) + fraction - off;
return ((ut1 + OFFSET_1900) / MIO_AS_DOUBLE);
}
// NTP-Timestamp als byte-Array kodieren
private static void encode(
byte[] data,
int pointer,
double timestamp
) {
// UT1-Mikrosekunden konstruieren
long ut1 = convert(timestamp);
boolean before2036 = (ut1 + OFFSET_2036 < 0);
long micros;
if (before2036) {
micros = ut1 + OFFSET_1900;
} else {
micros = ut1 + OFFSET_2036; // siehe RFC 4330, Abschnitt 3
}
// Festkomma vor Bit 32, deshalb Bits nach links schieben
long integer = micros / MIO;
long fraction = ((micros % MIO) << 32) / MIO;
if (before2036) {
integer |= 0x80000000L; // siehe RFC 4330, Abschnitt 3
}
long ntp = ((integer << 32) | fraction);
// Binäre Darstellung erzeugen
for (int i = 7; i >= 0; i--) {
data[i + pointer] = (byte) (ntp & 0xFF);
ntp >>>= 8;
}
// niedrigstes Byte nur als Zufallszahl (siehe RFC 4330, Abschnitt 3)
data[7 + pointer] = (byte) (Math.random() * BIT08);
}
// Byte-Konvertierung
private static short toUnsigned(byte b) {
short unsignedByte = b;
if ((b & 0x80) == 0x80) {
unsignedByte = (short) (128 + (b & 0x7f));
}
return unsignedByte;
}
}