<?xml version="1.0" encoding="utf-8"?>
<!-- name="GENERATOR" content="github.com/mmarkdown/mmark Mmark Markdown Processor - mmark.miek.nl" -->
<rfc version="3" ipr="trust200902" docName="draft-nightglow-oitp-00" submissionType="IETF" category="exp" xml:lang="en" xmlns:xi="http://www.w3.org/2001/XInclude" indexInclude="true">

<front>
<title abbrev="OITP">Open Internet Time Protocol (OITP)</title><seriesInfo value="draft-nightglow-oitp-00" stream="IETF" status="experimental" name="Internet-Draft"></seriesInfo>
<author fullname="Maxine"><address><postal><street></street>
</postal><email>max@nightglow.one</email>
</address></author><date year="2026" month="March" day="15"></date>
<area>Internet</area>

<keyword>decimal time</keyword>
<keyword>synchronization</keyword>
<keyword>beat</keyword>

<abstract>
<t>This document specifies the Open Internet Time Protocol (OITP), a
network time synchronization protocol for decimal time systems that
divide the civil day (86400 SI seconds) into 1000 primary units
called beats, each exactly 86.4 SI seconds.
OITP enables clients to synchronize decimal time clocks over
packet-switched networks with sub-beat precision, using techniques
derived from existing network time protocols adapted for the
decimal time domain.</t>
<t>The reference timescale uses UTC+1 as its reference meridian, with
midnight at UTC+1 corresponding to beat zero.</t>
</abstract>

</front>

<middle>

<section anchor="introduction"><name>Introduction</name>

<section anchor="background"><name>Background</name>
<t>Decimal time systems that divide the solar day into 1000 equal
parts have a long history, dating back to French Revolutionary
Time (1793). In 1998, Swatch introduced &quot;Swatch Internet Time&quot;
(.beat), which re-popularized this concept for the internet era,
proposing a universal time notation without time zones, where all
locations share the same numeric time value referenced to UTC+1
(BMT, Biel Mean Time).</t>
<t>Despite decades of software implementations in numerous
programming languages and operating systems, no network protocol
has been specified for synchronizing decimal time clocks between
hosts. Existing implementations derive their time locally from
system clocks, inheriting whatever accuracy and precision the
underlying operating system provides, but with no mechanism for
direct decimal-time synchronization.</t>
</section>

<section anchor="motivation"><name>Motivation</name>
<t>OITP provides a purpose-built synchronization mechanism for
decimal time, offering:</t>

<ul spacing="compact">
<li>Native decimal time wire format (clients never need to parse
or convert UTC timestamps)</li>
<li>Compact wire format optimized for the decimal time domain</li>
<li>Synchronization precision down to the nanobeat level
(~86.4 ns)</li>
<li>Compatibility with existing decimal time displays and
applications</li>
<li>A minimal, self-contained protocol implementable in around
1500 lines of code in most languages</li>
</ul>
<t>A server running OITP performs the UTC-to-decimal conversion
once, distributing the result directly. Clients receive native
decimal time with no conversion step, which reduces code size
for constrained devices. The protocol also
defines a shared synchronization layer for communities where
decimal time is the primary timekeeping system -- something a
local NTP-to-decimal conversion on each client cannot provide.</t>
</section>

<section anchor="relationship-to-ntp"><name>Relationship to NTP</name>
<t>An alternative approach would be to distribute decimal time as
an NTP extension field, piggybacking on NTP's existing
infrastructure and security mechanisms (NTS <xref target="RFC8915"></xref>). OITP
is a separate protocol for several reasons:</t>

<ul spacing="compact">
<li>NTP extension fields are processed by the client after UTC
time is already received; the decimal conversion still occurs
on every client. OITP moves the conversion to the server.</li>
<li>Decimal time requires a different notion of &quot;accuracy&quot;: a
1-millibeat error (~86ms) is often acceptable where NTP
targets microseconds. A dedicated protocol can have a simpler
algorithm tuned to this precision target.</li>
<li>OITP's wire format is native decimal: timestamps, poll
intervals, and delay fields are all expressed in beats and
millibeats. An NTP extension field would carry decimal time
embedded inside a UTC-based protocol, creating a conceptual
mismatch and complicating implementations.</li>
<li>A self-contained protocol is easier to implement on constrained
devices that may not need a full NTP stack.</li>
</ul>
<t>OITP does not replace NTP. Deployments are expected to use NTP
to synchronize the UTC reference clocks that OITP servers derive
their time from; this requirement is normatively stated in
Section 9.1.</t>
</section>

<section anchor="scope"><name>Scope</name>
<t>OITP is designed for decimal time synchronization over IP
networks. It is not intended to replace NTP <xref target="RFC5905"></xref> or PTP
<xref target="IEEE1588"></xref> for applications requiring UTC or TAI synchronization. OITP
servers derive their reference time from existing UTC sources
(typically NTP-synchronized system clocks or GPS/PPS receivers)
and serve it in the decimal time domain.</t>
</section>

<section anchor="document-organization"><name>Document Organization</name>
<t>Section 2 defines terminology. Section 3 describes the decimal
time system and notation. Section 4 specifies the 64-bit
timestamp format. Section 5 provides protocol overview and
transport. Section 6 defines the wire packet format. Sections 7
and 8 specify the synchronization algorithm and clock discipline.
Sections 9 and 10 define server and client behavior. Section 11
describes the stratum hierarchy. Section 12 addresses security
considerations. Section 13 covers IANA considerations.</t>
</section>
</section>

<section anchor="terminology"><name>Terminology</name>
<t>The key words &quot;MUST&quot;, &quot;MUST NOT&quot;, &quot;REQUIRED&quot;, &quot;SHALL&quot;,
&quot;SHALL NOT&quot;, &quot;SHOULD&quot;, &quot;SHOULD NOT&quot;, &quot;RECOMMENDED&quot;,
&quot;NOT RECOMMENDED&quot;, &quot;MAY&quot;, and &quot;OPTIONAL&quot; in this document are
to be interpreted as described in BCP 14 <xref target="RFC2119"></xref>
<xref target="RFC8174"></xref> when, and only when, they appear in capitalized
form, as shown here.</t>
<t>The following terms are used throughout this specification:</t>
<t><strong>beat</strong>: The primary unit of decimal time, equal to exactly
86.4 SI seconds, dividing the civil day (86400 SI seconds) into
1000 equal parts.</t>
<t><strong>millibeat</strong>: 1/1000 of a beat, equal to 86.4 milliseconds.</t>
<t><strong>microbeat</strong>: 1/1,000,000 of a beat, equal to
86.4 microseconds.</t>
<t><strong>nanobeat</strong>: 1/1,000,000,000 of a beat, equal to
86.4 nanoseconds.</t>
<t><strong>day epoch</strong>: The start of beat 0 for a given day, defined as
midnight at the reference meridian (UTC+1), which corresponds to
23:00:00 UTC of the preceding calendar day.</t>
<t><strong>system epoch</strong>: The reference point for absolute day numbering
in OITP, defined as 1998-10-23T00:00:00+01:00 (midnight UTC+1
on 23 October 1998), day number 0. This date corresponds to the
public launch of Swatch Internet Time, ensuring that OITP day
numbers align with existing .beat implementations.</t>
<t><strong>linearization</strong>: The process of converting an OITP 64-bit
timestamp to a uniform integer representation for arithmetic
operations. Because the beat field uses only values 0-999 (not
the full 10-bit range 0-1023), raw timestamp subtraction is
incorrect. See Section 7.1.</t>
<t><strong>Kiss-o'-Death (KoD)</strong>: A server response with stratum 3 and a
non-zero Reference ID containing a diagnostic code (Section 9.4),
signaling that the server is refusing or restricting service.</t>
<t><strong>precision</strong>: The inherent accuracy of a server's time source,
expressed as a signed integer representing floor(log2(E)) where
E is the maximum error in beats.</t>
</section>

<section anchor="decimal-time-system"><name>Decimal Time System</name>

<section anchor="time-division"><name>Time Division</name>
<t>The civil day (86400 SI seconds) is divided into exactly 1000 beats,
each exactly 86.4 SI seconds. Each beat
is subdivided according to SI-style decimal prefixes:</t>
<table>
<thead>
<tr>
<th>Unit</th>
<th>Beats</th>
<th>SI Seconds</th>
<th>Notation</th>
</tr>
</thead>

<tbody>
<tr>
<td>1 beat</td>
<td>1</td>
<td>86.4</td>
<td>@XXX</td>
</tr>

<tr>
<td>1 millibeat</td>
<td>0.001</td>
<td>0.0864</td>
<td>@XXX.XXX</td>
</tr>

<tr>
<td>1 microbeat</td>
<td>0.000001</td>
<td>0.0000864</td>
<td>-</td>
</tr>

<tr>
<td>1 nanobeat</td>
<td>0.000000001</td>
<td>0.0000000864</td>
<td>-</td>
</tr>
</tbody>
</table></section>

<section anchor="reference-meridian"><name>Reference Meridian</name>
<t>The decimal day begins (beat 0) at midnight at the UTC+1
meridian. This corresponds to 23:00:00 UTC of the preceding
civil day.</t>
<t>The decimal time value is globally uniform: at any given instant,
the beat value is the same for all observers regardless of
geographic location. There are no time zones or daylight saving
adjustments in decimal time.</t>
</section>

<section anchor="conversion-from-utc"><name>Conversion from UTC</name>
<t>Given a UTC time expressed as hours (h), minutes (m), and
seconds (s) with fractional part, the current beat value is:</t>

<artwork><![CDATA[beat = ((h + 1) * 3600 + m * 60 + s) / 86.4
]]></artwork>
<t>This formula is illustrative. The divisor 86.4 is not exactly
representable in IEEE 754 binary floating-point, which may
introduce rounding errors. Implementations <bcp14>SHOULD</bcp14> use the
integer-safe equivalent:</t>

<artwork><![CDATA[millibeats = ((h + 1) * 3600 + m * 60 + s) * 1000 / 86400
]]></artwork>
<t>or derive decimal time from continuous integer timescales (e.g.,
Unix nanoseconds) to avoid floating-point precision issues
entirely.</t>
<t>If the result is &gt;= 1000, subtract 1000 (the UTC+1 offset
causes a day boundary crossing at 23:00 UTC).</t>
<t>Note: This formula assumes exactly 86400 SI seconds per day and
does not account for UTC leap seconds. During a positive leap
second (23:59:60 UTC), the formula produces values beyond 1000
before wraparound, resulting in a brief discontinuity.
Implementations <bcp14>SHOULD</bcp14> derive decimal time from continuous
timescales (e.g., CLOCK_REALTIME on POSIX systems where the
kernel handles leap seconds via smearing or stepping) rather
than applying this formula directly to sexagesimal time
components.</t>
<t>During UTC leap second events, the conversion formula may produce
beat values outside 0-999. Servers <bcp14>SHOULD</bcp14> use a continuous
timescale that smears leap seconds (distributing the adjustment
over a window), preserving decimal time continuity. Servers
<bcp14>SHOULD NOT</bcp14> freeze or clamp beat values at 999, as this creates
a period of stopped time that may trigger false drift detection in
clients. The Leap Indicator flag (Section 6.2) signals pending
leap seconds; clients <bcp14>SHOULD</bcp14> increase their tolerance for
server disagreement when this flag is set.</t>
</section>

<section anchor="display-notation"><name>Display Notation</name>
<t>The <bcp14>RECOMMENDED</bcp14> display notation for decimal time uses the
&quot;@&quot; prefix followed by three digits for the integer beat,
optionally followed by a decimal point and fractional digits:</t>

<artwork><![CDATA[@000        midnight (UTC+1)
@500        midday (UTC+1)
@999        just before midnight
@248.573    beat 248, millibeat 573
]]></artwork>
<t>Implementations <bcp14>SHOULD</bcp14> display at least three digits for the
integer beat (zero-padded). The number of fractional digits is
implementation-defined based on available precision.</t>
</section>

<section anchor="combined-date-time-notation"><name>Combined Date-Time Notation</name>
<t>OITP defines two combined date-time notations. Both place the
&quot;@&quot; character immediately after the date component with no
intervening space, serving as the date-time separator.</t>

<section anchor="calendar-form"><name>Calendar Form</name>
<t>The calendar form uses the calendar date at UTC+1 with dot
separators. Its ABNF <xref target="RFC5234"></xref> grammar is:</t>

<artwork><![CDATA[calendar-form = year "." month "." day "@" beat "." millibeat
year          = 4DIGIT
month         = 2DIGIT          ; 01-12
day           = 2DIGIT          ; 01-31
beat          = 3DIGIT          ; 000-999
millibeat     = 3DIGIT          ; 000-999
DIGIT         = %x30-39         ; 0-9
]]></artwork>
<t>Examples:</t>

<artwork><![CDATA[2026.03.09@438.760  9 March 2026, beat 438, millibeat 760
1998.10.23@000.000  system epoch
]]></artwork>
<t>The date component uses dots (.) as separators rather than
hyphens to visually distinguish OITP date-time notation from
ISO 8601. The year <bcp14>MUST</bcp14> be four digits; the month and day
<bcp14>MUST</bcp14> be zero-padded to two digits. The date portion
<bcp14>MUST</bcp14> be the calendar date at the reference meridian (UTC+1).</t>
<t>The calendar form is <bcp14>RECOMMENDED</bcp14> for human-facing displays,
shared timestamps, log entries, and data interchange.</t>
</section>

<section anchor="day-form"><name>Day Form</name>
<t>The day form uses the OITP day number (Section 4.1) instead of a
calendar date. Its ABNF <xref target="RFC5234"></xref> grammar is:</t>

<artwork><![CDATA[day-form  = day-number "@" beat "." millibeat
day-number = "0" / (NZDIGIT *DIGIT)  ; no leading zeros except "0"
NZDIGIT   = %x31-39                  ; 1-9
]]></artwork>
<t>where <tt>day-number</tt> is a decimal integer with no leading zeros
(except for day 0 itself). The 24-bit protocol field supports
values up to 16,777,215.</t>
<t>Examples:</t>

<artwork><![CDATA[0@000.000         system epoch (1998-10-23)
9999@438.760      day 9999, beat 438, millibeat 760
10000@500.000     day 10000, beat 500
16777215@999.999  maximum representable day
]]></artwork>
<t>The day form is <bcp14>RECOMMENDED</bcp14> for protocol-level
representations, compact logging, and contexts where the OITP
day number is more meaningful than a calendar date. It is also
the most compact unambiguous representation of an OITP instant.</t>
</section>

<section anchor="usage"><name>Usage</name>
<t>When displaying only the time component (without date), the &quot;@&quot;
prefix notation (Section 3.4) <bcp14>SHOULD</bcp14> be used. When a full
date-time representation is needed, implementations <bcp14>SHOULD</bcp14>
support at least the calendar form. The day form <bcp14>MAY</bcp14> be
offered as an alternative.</t>
</section>
</section>
</section>

<section anchor="timestamp-representation"><name>Timestamp Representation</name>

<section anchor="oitp-timestamp-format"><name>OITP Timestamp Format</name>
<t>OITP uses a 64-bit unsigned integer timestamp with the following
structure:</t>

<sourcecode type="ascii-art"><![CDATA[ 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Day Number (24 bits)        |  Beat Int     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Beat Int cont.|         Beat Fraction (30 bits)               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
]]></sourcecode>
<t><strong>Day Number (24 bits)</strong>: Unsigned integer counting days elapsed
since the system epoch (1998-10-23). Range 0 to 16,777,215,
covering approximately 45,929 years.</t>
<t><strong>Beat Integer (10 bits)</strong>: The integer part of the current beat,
range 0 to 999. Beat integer values outside the range 0-999 are
invalid; receivers <bcp14>MUST</bcp14> discard packets containing
out-of-range beat integers. The remaining values 1000-1023
representable in 10 bits are reserved and <bcp14>MUST NOT</bcp14> be
transmitted.</t>
<t><strong>Beat Fraction (30 bits)</strong>: The fractional part of the current
beat as a 30-bit unsigned fixed-point number. The value
represents the fraction N/2^30 of one beat. This provides a
resolution of approximately 0.00000000093 beats, or roughly
0.93 nanobeats, exceeding the nanobeat precision target.</t>
</section>

<section anchor="design-rationale"><name>Design Rationale</name>
<t>The 10-bit beat integer field uses only 1000 of 1024 possible
values, making the raw 64-bit timestamp non-linear. An
alternative design could use a single 40-bit fractional day
field (linear), but the current format allows direct inspection
of the day number and beat value in a hex dump or packet capture
without computation. This human-readable wire format follows the
same design philosophy as NTP's split seconds/fraction fields.
The linearization cost (Section 7.1) is a single multiply-add
per timestamp, which is negligible.</t>
</section>

<section anchor="resolution-and-range"><name>Resolution and Range</name>
<t>The 30-bit fractional field provides:</t>

<ul spacing="compact">
<li>Resolution: 1/1,073,741,824 of a beat = ~0.93 nanobeats
= ~80.5 ns</li>
<li>This is comparable to NTP's 32-bit fractional second field,
which provides ~233 picosecond resolution</li>
</ul>
<t>The 24-bit day field provides:</t>

<ul spacing="compact">
<li>Range: 16,777,216 days = ~45,929 years from epoch</li>
<li>The epoch (1998-10-23) places the end of range at
approximately year 47960, which is considered sufficient</li>
</ul>
</section>

<section anchor="special-values"><name>Special Values</name>
<t>A timestamp of all zeros (day 0, beat 0, fraction 0) represents
the system epoch itself (1998-10-23T00:00:00+01:00). A timestamp
of all ones (0xFFFFFFFFFFFFFFFF) is reserved and <bcp14>MUST NOT</bcp14> be
used in protocol messages; it indicates an invalid or
uninitialized timestamp.</t>
<t>In protocol fields where zero indicates &quot;not set&quot; or &quot;not
applicable&quot; (e.g., the Origin and Receive Timestamp fields of a
client request), this usage takes precedence: the value zero in
those fields means the field is absent, not that the timestamp
equals the epoch. Receivers <bcp14>MUST NOT</bcp14> interpret a zero in
a &quot;not set&quot; field as a valid epoch timestamp.</t>
</section>
</section>

<section anchor="protocol-overview"><name>Protocol Overview</name>

<section anchor="transport"><name>Transport</name>
<t>OITP operates over UDP <xref target="RFC768"></xref> for time synchronization. The
<bcp14>RECOMMENDED</bcp14> port number is 8640 (see Section 13 for IANA
registration).</t>
<t>Implementations <bcp14>MAY</bcp14> additionally expose an HTTP interface
for lightweight, human-readable time queries (see Appendix D).
The HTTP interface is not part of the synchronization protocol
and does not provide offset or delay calculation.</t>
</section>

<section anchor="operating-modes"><name>Operating Modes</name>
<t>OITP defines two operating modes:</t>
<t><strong>Basic mode</strong>: A client sends a request and receives a response
containing the server's current decimal time. This mode provides
time transfer without round-trip delay compensation. Suitable
for applications where beat-level accuracy is sufficient.</t>
<t><strong>Full mode</strong>: The default mode. A client and server
exchange timestamps allowing computation of clock offset and
network delay, similar to NTP's client-server mode. This mode
provides sub-millibeat precision on typical networks.</t>
</section>

<section anchor="protocol-exchange"><name>Protocol Exchange</name>
<t>In full mode, a single request-response exchange proceeds
as follows:</t>

<ol>
<li><t>At time T1, the client records its local decimal timestamp
and sends a request packet containing T1 in the Transmit
Timestamp field.</t>
</li>
<li><t>At time T2, the server receives the request and records T2.</t>
</li>
<li><t>At time T3, the server sends a response packet containing
T1 (copied from the request), T2, and T3.</t>
</li>
<li><t>At time T4, the client receives the response and records T4.</t>
</li>
</ol>
<t>The client then computes the clock offset and round-trip delay
using the four timestamps.</t>
</section>
</section>

<section anchor="packet-format"><name>Packet Format</name>

<section anchor="header"><name>Header</name>
<t>All multi-octet fields in OITP packets <bcp14>MUST</bcp14> be transmitted in
network byte order (big-endian). Bit 0 of each field is the most
significant bit (MSB-first), following the IETF convention
established in <xref target="RFC791"></xref>.</t>
<t>All OITP packets share a common format:</t>

<sourcecode type="ascii-art"><![CDATA[ 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
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| VN  | M |L|St |  Precision    |         Poll Interval         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Root Delay (32 bits)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Root Dispersion (32 bits)                   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Reference ID (32 bits)                      |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                 Reference Timestamp (64 bits)                 |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                  Origin Timestamp (64 bits)                   |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                 Receive Timestamp (64 bits)                   |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                                                               |
|                 Transmit Timestamp (64 bits)                  |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
]]></sourcecode>
<t>Total packet size: 48 octets (same as NTP for ease of
implementation on shared infrastructure).</t>
</section>

<section anchor="field-descriptions"><name>Field Descriptions</name>
<t><strong>Version Number (VN, 3 bits)</strong>: The protocol version number.
This specification defines version 1. Implementations <bcp14>MUST</bcp14>
set this field to 1.</t>
<t><strong>Mode (M, 2 bits)</strong>: The operating mode of the sender:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Mode</th>
</tr>
</thead>

<tbody>
<tr>
<td>0</td>
<td>Reserved</td>
</tr>

<tr>
<td>1</td>
<td>Basic Client</td>
</tr>

<tr>
<td>2</td>
<td>Full Client</td>
</tr>

<tr>
<td>3</td>
<td>Server</td>
</tr>
</tbody>
</table><t><strong>Leap Indicator (L, 1 bit)</strong>: Set to 1 if the server's UTC
source indicates a pending leap second within the current UTC day
(i.e., a leap second is scheduled at 23:59:60 UTC of the current
day). The flag <bcp14>SHOULD</bcp14> be asserted no earlier than beat 0 of
the affected day and <bcp14>SHOULD</bcp14> be cleared once the leap second
has passed. Since decimal time does not observe leap seconds,
this serves as an advisory flag only. Clients <bcp14>MAY</bcp14> use this
to flag reduced accuracy during the leap second adjustment period.</t>
<t><strong>Stratum (St, 2 bits)</strong>: The distance from the reference
source:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Meaning</th>
</tr>
</thead>

<tbody>
<tr>
<td>0</td>
<td>Reference clock (GPS/PPS-disciplined)</td>
</tr>

<tr>
<td>1</td>
<td>NTP-synchronized or UTC reference</td>
</tr>

<tr>
<td>2</td>
<td>Synchronized to stratum 0 or 1 server</td>
</tr>

<tr>
<td>3</td>
<td>Unsynchronized / Kiss-o'-Death (9.4)</td>
</tr>
</tbody>
</table><t><strong>Precision (8 bits)</strong>: Signed integer (two's complement) representing
the precision of the server's clock, expressed as floor(log2(E))
where E is the maximum error expressed in beats. For example:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Precision</th>
<th>Approximate Error</th>
</tr>
</thead>

<tbody>
<tr>
<td>-10</td>
<td>~0.001 beats</td>
<td>~1 millibeat</td>
</tr>

<tr>
<td>-20</td>
<td>~0.000001 beats</td>
<td>~1 microbeat</td>
</tr>

<tr>
<td>-30</td>
<td>~1 nanobeat</td>
<td>~86.4 nanoseconds</td>
</tr>
</tbody>
</table><t>A typical NTP-backed server would advertise precision of
approximately -13 to -16 (sub-millibeat).</t>
<t><strong>Poll Interval (16 bits)</strong>: Unsigned integer indicating the
<bcp14>RECOMMENDED</bcp14> minimum interval between client requests,
expressed in beats. A value of 0 indicates no recommendation.
Servers under load <bcp14>SHOULD</bcp14> set this field to request reduced
query rates. Clients <bcp14>SHOULD</bcp14> set this field to 0 in requests
(the field carries server-to-client guidance; clients have no
poll interval to communicate in a single request).</t>
<t><strong>Root Delay (32 bits)</strong>: Total round-trip delay to the primary
reference source, expressed as a 16.16 fixed-point number in
beats. The integer part occupies bits 31-16 and the fractional
part occupies bits 15-0; a value of 0x00010000 represents exactly
1 beat (86.4 seconds). Clients <bcp14>SHOULD</bcp14> use this field for server
selection (Section 11.2) when multiple servers are available;
single-server clients <bcp14>MAY</bcp14> ignore it.</t>
<t><strong>Root Dispersion (32 bits)</strong>: Total dispersion to the primary
reference source, expressed as a 16.16 fixed-point number in
beats, using the same encoding as Root Delay. Clients <bcp14>SHOULD</bcp14>
use this field for server selection (Section 11.2); single-server
clients <bcp14>MAY</bcp14> ignore it.</t>
<t><strong>Reference ID (32 bits)</strong>: Identifies the particular reference
source. For stratum 0 and stratum 1, this is a four-character
ASCII string (left-justified, zero-padded) identifying the
reference source type:</t>
<table>
<thead>
<tr>
<th>ID</th>
<th>Source</th>
</tr>
</thead>

<tbody>
<tr>
<td>GPS\0</td>
<td>GPS or GNSS hardware reference clock</td>
</tr>

<tr>
<td>PPS\0</td>
<td>Pulse-per-second hardware reference</td>
</tr>

<tr>
<td>NTP\0</td>
<td>Time derived from an NTP source</td>
</tr>
</tbody>
</table><t>For stratum 2, this field contains the IPv4 address of the
upstream OITP server, or the first 32 bits of the SHA-256
<xref target="RFC6234"></xref> hash of the upstream server's IPv6 address
(16-byte binary representation per <xref target="RFC8200"></xref>).
For IPv4-mapped IPv6 addresses (::ffff:0:0/96), implementations
<bcp14>MUST</bcp14> use the embedded IPv4 address (the final 4 octets) directly
rather than hashing the full 16-byte IPv6 representation, to ensure
consistent Reference IDs across dual-stack deployments. This serves
only as a non-cryptographic identifier for loop detection; collision
resistance is not a security requirement.</t>
<t>For stratum 3, this field contains an ASCII diagnostic code
(Section 9.4) for a Kiss-o'-Death response, or zero for
unsynchronized clocks. Clients <bcp14>MUST</bcp14> treat a non-zero
Reference ID in a stratum 3 response as a KoD code and handle
it per Section 9.4; a stratum 3 response with a zero Reference
ID indicates an unsynchronized server and <bcp14>MUST NOT</bcp14> be used
for synchronization.</t>
<t><strong>Reference Timestamp (64 bits)</strong>: The time at which the
server's clock was last set or corrected, in OITP timestamp
format (Section 4). Servers <bcp14>SHOULD</bcp14> update this field after
each successful clock discipline operation.</t>
<t><strong>Origin Timestamp (64 bits)</strong>: In a server response, this field
contains the client's transmit timestamp copied from the request
(Section 9.2). In a client request, this field <bcp14>SHOULD</bcp14> be set
to zero. The client's local time is carried in the Transmit
Timestamp field instead (Section 10.2).</t>
<t><strong>Receive Timestamp (64 bits)</strong>: The decimal time at which the
request was received by the server. Set to zero in client
requests.</t>
<t><strong>Transmit Timestamp (64 bits)</strong>: The decimal time at which the
packet was sent. In Full mode (Section 10.3), both clients and
servers <bcp14>MUST</bcp14> set this field to their local decimal time at
the moment of transmission. A Full mode request with a zero
Transmit Timestamp is invalid; servers <bcp14>SHOULD</bcp14> discard such
requests silently. In Basic Client mode (Section 10.2),
the value zero is a valid transmit timestamp; the server echoes
it back in the origin field without interpretation, and no offset
or delay calculation is performed.</t>
</section>
</section>

<section anchor="synchronization-algorithm"><name>Synchronization Algorithm</name>

<section anchor="offset-and-delay-calculation"><name>Offset and Delay Calculation</name>
<t>Given the four timestamps from a full mode exchange (T1,
T2, T3, T4 all in OITP 64-bit format), the clock offset theta
and round-trip delay delta are computed as:</t>

<artwork><![CDATA[theta = ((T2 - T1) + (T3 - T4)) / 2
delta = (T4 - T1) - (T3 - T2)
]]></artwork>
<t>Implementations <bcp14>MUST</bcp14> discard samples where the computed
round-trip delay (delta) is negative, as this indicates
inconsistent timestamps caused by path asymmetry changes, packet
reordering, or malicious modification.</t>
<t>Because the 10-bit beat integer field uses only values 0-999
(not the full 0-1023 range), the raw 64-bit timestamp is not a
uniform linear representation. Implementations <bcp14>MUST</bcp14>
linearize timestamps before arithmetic by converting to:
day * 1000 * 2^30 + beat * 2^30 + fraction. The results are in
beat-fraction units where one beat equals 2^30 units. The maximum
linearized value (day=16,777,215, beat=999, frac=2^30-1) is
approximately 1.80 * 10^19, which exceeds the range of a signed
64-bit integer (maximum 9.22 * 10^18). Implementations <bcp14>MUST</bcp14>
therefore use wider-than-64-bit arithmetic (128-bit integers or
arbitrary precision) for linearized timestamp values. However,
timestamp <strong>differences</strong> for practical synchronization (exchanges
spanning fewer than approximately 8,500 days) fit in a signed
64-bit integer; implementations that restrict their operational
window accordingly <bcp14>MAY</bcp14> use 64-bit arithmetic for differences
only. On platforms where the default integer width is 32 bits,
intermediate products <bcp14>MUST</bcp14> be explicitly cast to a wider type
before multiplication to avoid silent overflow. Packets that would
produce an out-of-range beat integer after linearization <bcp14>MUST</bcp14>
be discarded per Section 4.1.</t>
<t>The offset theta represents the estimated correction needed to
bring the client's clock into alignment with the server. A
positive value indicates the client is behind; a negative value
indicates the client is ahead.</t>
<t>All four timestamp differences (T2-T1, T3-T4, T4-T1, T3-T2)
<bcp14>MUST</bcp14> be computed as signed 64-bit integers. Although linearized
timestamps are non-negative, their differences can be negative, and
implementations using unsigned 64-bit types will produce incorrect
results due to wrap-around.</t>
<t>To apply a computed offset to a local timestamp, the inverse
of the linearization formula is:</t>

<artwork><![CDATA[linear  = day * 1000 * 2^30 + beat * 2^30 + frac
day     = floor(linear / (1000 * 2^30))
remainder = linear mod (1000 * 2^30)   ; non-negative
beat    = floor(remainder / 2^30)
frac    = remainder mod 2^30
]]></artwork>
<t>where mod denotes the non-negative (floored) modulo operation.
Implementations <bcp14>MUST</bcp14> use floor division (toward negative
infinity) rather than truncation division (toward zero) to
ensure correct de-linearization for negative intermediate values
on signed platforms.</t>
</section>

<section anchor="filtering"><name>Filtering</name>
<t>Implementations <bcp14>SHOULD</bcp14> maintain a window of the most recent
N exchange results (<bcp14>RECOMMENDED</bcp14> N=8) and apply a selection
algorithm to reject outliers. The sample with the minimum delay
in the window is <bcp14>RECOMMENDED</bcp14> as the best estimate, as lower
delay generally correlates with more symmetric paths and
therefore more accurate offset estimates. When two samples have
equal delay, either may be selected; implementations
<bcp14>SHOULD</bcp14> prefer the more recent sample as it reflects current
network conditions.</t>
</section>

<section anchor="day-boundary-handling"><name>Day Boundary Handling</name>
<t>Special care <bcp14>MUST</bcp14> be taken when timestamps in a single
exchange span a day boundary (beat rolling from 999.xxx to
000.xxx). Implementations <bcp14>MUST</bcp14> compare day numbers in the
timestamps and adjust calculations accordingly. An exchange
spanning a day boundary is still valid provided the total
round-trip delay is less than 500 beats (12 hours).</t>
</section>

<section anchor="precision-limits"><name>Precision Limits</name>
<t>The achievable synchronization precision depends on the server's
stratum and the network characteristics:</t>
<table>
<thead>
<tr>
<th>Scenario</th>
<th>Expected Precision</th>
</tr>
</thead>

<tbody>
<tr>
<td>LAN, stratum 0 server</td>
<td>~0.01 millibeats (~1ms)</td>
</tr>

<tr>
<td>WAN, stratum 0 server</td>
<td>~0.1-1 millibeats</td>
</tr>

<tr>
<td>WAN, stratum 1 server</td>
<td>~1-10 millibeats</td>
</tr>
</tbody>
</table><t>These figures assume typical network jitter and are provided for
guidance only.</t>
</section>
</section>

<section anchor="clock-discipline"><name>Clock Discipline</name>

<section anchor="initial-synchronization"><name>Initial Synchronization</name>
<t>When an OITP client starts with no prior state, it <bcp14>SHOULD</bcp14>
send a burst of 4 requests at short intervals (<bcp14>RECOMMENDED</bcp14>
2 seconds apart) to rapidly acquire an initial time estimate.
The best sample (minimum delay) from this burst <bcp14>SHOULD</bcp14> be
used for the initial clock step. If all burst queries fail to
elicit a valid response (e.g., due to network unreachability),
the client <bcp14>SHOULD</bcp14> retry the burst after a backoff interval
(<bcp14>RECOMMENDED</bcp14> starting at 16 beats, doubling on each
consecutive failure) rather than proceeding with an
unsynchronized clock.</t>
</section>

<section anchor="state-persistence"><name>State Persistence</name>
<t>Implementations on devices with non-volatile storage
<bcp14>SHOULD</bcp14> persist the last known good clock state (day number,
approximate beat, and the estimated drift rate if tracked) across
restarts. On restart, a persisted state <bcp14>MAY</bcp14> be used to skip
the initial burst if the elapsed time since the last save is
short and the confidence interval is acceptable, subject to
implementation-defined bounds. This is particularly valuable
for battery-powered or constrained devices where network
operations are expensive.</t>
</section>

<section anchor="steady-state-operation"><name>Steady-State Operation</name>
<t>After initial synchronization, the client <bcp14>SHOULD</bcp14> poll the
server at regular intervals. The <bcp14>RECOMMENDED</bcp14> default poll
interval is 64 beats (approximately 92 minutes).
Implementations <bcp14>MAY</bcp14> adjust the poll interval dynamically
based on observed clock drift and network conditions, within
the range of 16 beats (~23 minutes) to 1000 beats (~24 hours).</t>
</section>

<section anchor="clock-adjustment"><name>Clock Adjustment</name>
<t>For offsets smaller than 1 beat, implementations <bcp14>SHOULD</bcp14> slew
the clock gradually rather than stepping it, to avoid
discontinuities in the local decimal time. A slew rate of
0.5 millibeats per beat (approximately 43.2 ms/86.4s, or
500 ppm) is <bcp14>RECOMMENDED</bcp14>. When a new offset correction is
computed while a previous slew is still in progress, the new
correction <bcp14>SHOULD</bcp14> replace the remaining slew rather than
accumulate with it. This is correct because the new measurement's
timestamps (T1, T4) already reflect any partial slew applied since
the previous correction.</t>
<t>For offsets larger than 1 beat, implementations <bcp14>SHOULD</bcp14> step
the clock immediately, as slewing would take an impractical
amount of time. Applications that read the clock continuously
(e.g., for display) <bcp14>SHOULD</bcp14> be designed to tolerate sudden
jumps at step events; implementations <bcp14>MAY</bcp14> emit a local
notification (e.g., a log message or signal) when a step
correction is applied, to allow dependent subsystems to react.</t>
</section>

<section anchor="panic-threshold"><name>Panic Threshold</name>
<t>If the offset exceeds 50 beats (approximately 72 minutes), the
client <bcp14>SHOULD</bcp14> assume a severe error condition and refuse to
adjust the clock automatically. Operator intervention or a full
re-initialization is <bcp14>RECOMMENDED</bcp14>.</t>
<t>Implementations <bcp14>MAY</bcp14> track the clock frequency error (drift
rate) between successive offset corrections and apply predictive
corrections between polls to reduce inter-poll drift. Frequency
discipline algorithms are beyond the scope of this document and
are expected to be addressed in a future revision of this
specification.</t>
</section>
</section>

<section anchor="server-operations"><name>Server Operations</name>

<section anchor="reference-clock-derivation"><name>Reference Clock Derivation</name>
<t>An OITP server derives its decimal time from a UTC reference
source. The server <bcp14>MUST</bcp14> maintain an accurate
UTC-to-decimal-time conversion, applying the formula in
Section 3.3 with the highest precision available from its UTC
source. When the server operates with its own clock discipline
loop (e.g., synchronizing to an upstream OITP or NTP server),
timestamps in responses <bcp14>MUST</bcp14> reflect the clock-corrected
time rather than the raw system clock, so that accumulated
corrections benefit clients immediately.</t>
<t>OITP servers <bcp14>MUST</bcp14> derive their time from a traceable UTC
source (e.g., a GPS/PPS receiver, or an NTP-synchronized system
clock). A server <bcp14>MUST NOT</bcp14> advertise stratum 0 or 1 unless
it is in fact synchronized to such a source.</t>
<t>Servers with direct access to GPS or PPS reference clocks
<bcp14>SHOULD</bcp14> operate at stratum 0. Servers deriving time from
NTP-synchronized system clocks <bcp14>SHOULD</bcp14> operate at stratum 1.</t>
</section>

<section anchor="request-processing"><name>Request Processing</name>
<t>Upon receiving a packet, the server <bcp14>MUST</bcp14> silently discard
packets where:</t>

<ul spacing="compact">
<li>The version field is not 1</li>
<li>The mode field is not 1 (Basic Client) or 2 (Full Client)</li>
<li>The packet length is less than 48 octets</li>
</ul>
<t>In particular, servers receiving mode 3 (Server) packets <bcp14>MUST</bcp14>
discard them silently to prevent reflection attacks. The server
<bcp14>MUST NOT</bcp14> send an error response to discarded packets; silent
discard prevents information disclosure and avoids amplification
of malformed traffic.</t>
<t>Upon receiving a valid client request, the server:</t>

<ol spacing="compact">
<li>Records the receive timestamp T2</li>
<li>Sets the mode field to 3 (Server) in the response, regardless
of the client's mode field (Basic or Full)</li>
<li>Copies the client's transmit timestamp to the origin
timestamp field</li>
<li>Sets the receive timestamp field to T2</li>
<li>Sets all server metadata fields (stratum, precision,
reference ID, root delay, root dispersion)</li>
<li>Records the transmit timestamp T3 as late as possible before
sending</li>
<li>Sends the response</li>
</ol>
<t>The server <bcp14>SHOULD</bcp14> minimize processing time between recording
T2 and T3 to reduce uncertainty.</t>
</section>

<section anchor="rate-limiting"><name>Rate Limiting</name>
<t>Servers <bcp14>SHOULD</bcp14> implement rate limiting to prevent abuse. A
reasonable default is to allow no more than one request per beat
(86.4 seconds) per source IP address during steady-state
operation. To accommodate initial synchronization bursts
(Section 8.1), servers <bcp14>SHOULD</bcp14> allow a short burst of up to
8 requests from a previously unseen source IP before enforcing
the per-beat rate limit. Servers <bcp14>MAY</bcp14> use the poll interval
field to communicate desired polling rates to clients.</t>
</section>

<section anchor="kiss-o-death"><name>Kiss-o'-Death</name>
<t>If a server wishes to deny service to a client, it <bcp14>MUST</bcp14>
respond with stratum 3 and a Reference ID containing an ASCII
diagnostic code:</t>
<table>
<thead>
<tr>
<th>Code</th>
<th>Meaning</th>
</tr>
</thead>

<tbody>
<tr>
<td>DENY</td>
<td>Access denied</td>
</tr>

<tr>
<td>RATE</td>
<td>Rate limit exceeded</td>
</tr>

<tr>
<td>RSTR</td>
<td>Access restricted</td>
</tr>

<tr>
<td>STEP</td>
<td>Server stepping clock</td>
</tr>
</tbody>
</table><t>Clients receiving a DENY or RSTR response <bcp14>SHOULD</bcp14>
cease querying that server. Clients receiving a
STEP response <bcp14>SHOULD</bcp14> wait one full poll interval before
retrying, as the server is in the process of adjusting its
own clock. Clients receiving a
RATE response <bcp14>SHOULD</bcp14> double their poll interval and retry
after the increased interval; if RATE is received again, the
client <bcp14>SHOULD</bcp14> continue doubling up to the maximum poll
interval before ceasing queries. Because this version of OITP
lacks authentication, clients <bcp14>SHOULD NOT</bcp14> permanently
blacklist a server based on a single KoD response;
implementations <bcp14>SHOULD</bcp14> require consistent KoD responses
across multiple poll cycles before ceasing queries
permanently, to mitigate spoofed KoD denial-of-service
attacks (Section 12.2).</t>
</section>
</section>

<section anchor="client-operations"><name>Client Operations</name>

<section anchor="server-selection"><name>Server Selection</name>
<t>Clients <bcp14>SHOULD</bcp14> be configured with at least one OITP server
address. Server addresses <bcp14>MAY</bcp14> be specified as hostnames or
as IP address literals (IPv4 dotted-decimal or IPv6 bracketed
notation); constrained devices without access to a DNS resolver
<bcp14>SHOULD</bcp14> use IP address literals directly to avoid the
dependency on DNS resolution. When multiple servers are
available, clients <bcp14>SHOULD</bcp14> query all of them and use standard
intersection and clustering algorithms to select the best source
and reject falsetickers.</t>
</section>

<section anchor="basic-mode-client-behavior"><name>Basic Mode Client Behavior</name>
<t>In basic mode, the client:</t>

<ol spacing="compact">
<li>Sets mode to 1 (basic client)</li>
<li>Sets stratum to 3 (unsynchronized)</li>
<li>Sets the transmit timestamp field to zero (or any value; the
server copies it to the origin field but it is not used for
offset calculation)</li>
<li>Sets all other fields to zero</li>
<li>Sends the request to the server on port 8640</li>
</ol>
<t>Upon receiving a response, the client <bcp14>MUST</bcp14> verify that the
version field is 1 and the mode field is 3 (Server) before
using the response. The client then reads the server's transmit
timestamp (T3) as the current decimal time. No offset or delay
compensation is applied. This mode provides beat-level accuracy
suitable for display purposes where sub-beat precision is not
required. Basic mode is particularly appropriate for
battery-powered or highly constrained devices where minimizing
computation and state is more important than sub-beat precision.</t>
<t>Basic mode clients <bcp14>MUST NOT</bcp14> interpret stratum 3 responses as
Kiss-o'-Death. Basic mode clients <bcp14>SHOULD</bcp14> silently discard any
response with stratum 3. Since basic mode uses a zero or
predictable transmit timestamp, the origin timestamp match
provides no protection against spoofed KoD responses.</t>
</section>

<section anchor="full-mode-client-behavior"><name>Full Mode Client Behavior</name>
<t>In full mode, the client:</t>

<ol spacing="compact">
<li>Records its current local decimal time as T1</li>
<li>Sets the transmit timestamp field to T1</li>
<li>Sets mode to 2 (full)</li>
<li>Sets stratum to 3 (unsynchronized) if the client has not
yet synchronized, or to the client's current stratum
otherwise</li>
<li><t>Sets the remaining fields as follows:</t>

<ul spacing="compact">
<li>Precision: the client's estimated clock precision if
synchronized, or 0 if unsynchronized</li>
<li>Poll Interval: 0</li>
<li>Root Delay: 0x00000000</li>
<li>Root Dispersion: 0x00000000</li>
<li>Reference ID: 0x00000000</li>
<li>Reference Timestamp: 0x0000000000000000</li>
</ul></li>
<li>Sends the request to the server on port 8640</li>
</ol>
</section>

<section anchor="response-validation"><name>Response Validation</name>
<t>Upon receiving a response, the client <bcp14>MUST</bcp14> verify:</t>

<ol spacing="compact">
<li>The source address of the response matches the destination
address of the corresponding request</li>
<li>The version number is recognized (clients <bcp14>MUST</bcp14> discard
responses with an unrecognized version number)</li>
<li>The mode field is 3 (server)</li>
<li>The origin timestamp matches the T1 sent in the request</li>
<li>The stratum is not 3 with a kiss-o'-death reference ID</li>
<li>The transmit timestamp is non-zero</li>
<li>All timestamp fields in the response (receive timestamp and
transmit timestamp) <bcp14>MUST</bcp14> have beat integer values in the
range 0-999. Packets with out-of-range beat integers <bcp14>MUST</bcp14>
be discarded.</li>
</ol>
<t>If any check fails, the response <bcp14>MUST</bcp14> be discarded.</t>
</section>

<section anchor="display"><name>Display</name>
<t>Clients providing user-facing time display <bcp14>SHOULD</bcp14> format the
current decimal time according to Section 3.4. The number of
fractional digits displayed <bcp14>SHOULD</bcp14> reflect the actual
synchronization precision achieved, not the protocol's
theoretical maximum.</t>
</section>
</section>

<section anchor="precision-hierarchy"><name>Precision Hierarchy</name>

<section anchor="stratum-levels"><name>Stratum Levels</name>
<t>OITP uses a simplified stratum hierarchy compared to NTP:</t>
<t><strong>Stratum 0</strong>: The server has a direct hardware reference clock
(GPS receiver with PPS output) and performs the
UTC-to-decimal conversion locally. Expected precision:
microbeats to nanobeats.</t>
<t><strong>Stratum 1</strong>: The server derives its time from NTP (or another
UTC source) and converts to decimal time. The precision is
bounded by the NTP synchronization quality, typically
sub-millibeat on well-connected hosts.</t>
<t><strong>Stratum 2</strong>: The server synchronizes to a stratum 0 or
stratum 1 OITP server. Each additional hop adds uncertainty
from the OITP synchronization process. This is the maximum
operational stratum; further cascading is not supported by the
2-bit field. A stratum 2 server <bcp14>MUST NOT</bcp14> synchronize to
another stratum 2 server. Allowing stratum 2 chains would create
unbounded hierarchy depth invisible to clients.</t>
<t>Version 1 of OITP targets small-to-medium deployments where
one or two hops of OITP-native hierarchy is sufficient. A
typical deployment consists of one or more stratum 0 servers
(GPS/PPS-disciplined) with clients and stratum 1/2 servers
synchronizing to them. The 2-bit stratum field is a deliberate
simplicity trade-off for this scope.</t>
<t>Should future deployments require deeper native OITP hierarchies
(e.g., large-scale networks of independent reference clocks), a
subsequent version of this protocol could extend the stratum
field. The 3-bit version number field (Section 6.2) and the
extension mechanism (Section 12.8) provide the necessary upgrade
path.</t>
<t><strong>Stratum 3</strong>: Reserved for unsynchronized clocks and
Kiss-o'-Death (KoD) responses. A stratum 3 packet with a
non-zero Reference ID is a KoD; clients <bcp14>MUST</bcp14> handle it per
Section 9.4 regardless of whether the specific code is
recognized (unknown codes <bcp14>SHOULD</bcp14> be treated as DENY). A
stratum 3 packet with a zero Reference ID indicates an
unsynchronized server. Clients <bcp14>MUST NOT</bcp14>
synchronize to stratum 3 servers. Servers <bcp14>MUST NOT</bcp14> advertise
stratum 3 during normal operation. A server transitioning to
stratum 3 (e.g., upon loss of its reference source) <bcp14>MUST</bcp14> set
the Reference ID to zero unless it is intentionally sending a
Kiss-o'-Death response. Failure to clear a stale Reference ID
would cause clients to misinterpret the response as KoD.</t>
</section>

<section anchor="stratum-selection"><name>Stratum Selection</name>
<t>Clients <bcp14>SHOULD</bcp14> prefer lower-stratum servers. When multiple
servers of the same stratum are available, the server with the
lowest root delay and root dispersion <bcp14>SHOULD</bcp14> be preferred.</t>
</section>
</section>

<section anchor="security-considerations"><name>Security Considerations</name>

<section anchor="rate-limiter-hash-collisions"><name>Rate Limiter Hash Collisions</name>
<t>Server implementations that use hash-based per-IP rate limiting
(e.g., a direct-mapped hash table indexed by a hash of the
source address) are subject to hash collisions: two distinct
client addresses that hash to the same slot will share a rate
limit bucket, potentially causing one client to be rate-limited
due to the other's traffic. This is an implementation artifact
and not a protocol vulnerability. Implementations with stringent
fairness requirements <bcp14>SHOULD</bcp14> use larger tables or
collision-resistant data structures.</t>
</section>

<section anchor="spoofing"><name>Spoofing</name>
<t>OITP, like NTP, is vulnerable to spoofed responses from off-path
attackers when running over plain UDP. Source address validation is normatively required by
Section 10.4. The origin
timestamp matching (Section 10.4) provides a weak form of
authentication against blind spoofing. In particular, an
attacker can spoof Kiss-o'-Death responses (Section 9.4) to
cause clients to cease querying legitimate servers. Section
9.4 mitigates this by requiring multiple consistent KoD
responses before permanent cessation.</t>
<t>The stratum, root delay, root dispersion, and precision fields are
self-reported and unauthenticated. A malicious server can advertise
optimal values (e.g., stratum 0 with minimal root delay) to be
preferentially selected by clients. Multi-server deployments
mitigate this through cross-validation of server claims.</t>
</section>

<section anchor="on-path-attacks"><name>On-Path Attacks</name>
<t>An on-path attacker who can intercept and modify OITP packets
in transit can manipulate any or all of the four timestamps
(T1-T4) to shift the client's clock by an arbitrary amount.
This is a fundamental limitation of unauthenticated time
synchronization protocols; NTP <xref target="RFC5905"></xref> has the same
vulnerability.</t>
<t>Applications <bcp14>MUST NOT</bcp14> rely on OITP for security-sensitive
timekeeping (e.g., certificate validity checking, session token
expiry, audit log correlation) unless an authenticated mode is
used. A future authentication extension (Section 12.7) would
mitigate this attack. Until such an extension is standardized,
deployments requiring tamper-resistant time synchronization
<bcp14>SHOULD</bcp14> use NTS-protected NTP <xref target="RFC8915"></xref>.</t>
<t>An on-path attacker or compromised server can gradually shift a
client's clock by applying small offsets (less than the step
threshold of 1 beat) at each poll cycle. The clock discipline
algorithm will slew these adjustments without triggering any alarm.
Over successive polls, the client's clock can drift arbitrarily far
from true time. Deployments using a single server have no defense
against this attack. Implementations <bcp14>SHOULD</bcp14> query multiple
independent servers and apply intersection algorithms
(Section 10.1) to detect and reject sources providing inconsistent
time.</t>
</section>

<section anchor="amplification"><name>Amplification</name>
<t>The OITP UDP packet format is symmetric (48 bytes request, 48
bytes response), providing no amplification factor. This makes
OITP unsuitable as a DDoS amplification vector via UDP. The
optional HTTP interface (Appendix D) operates over TCP, which
requires a three-way handshake that prevents source address
spoofing; HTTP responses cannot be reflected to a forged source
address.</t>
</section>

<section anchor="privacy"><name>Privacy</name>
<t>OITP requests do not inherently contain identifying information
beyond the source IP address. However, the transmit timestamp
reveals the client's current local decimal time estimate, which
could theoretically be used to fingerprint client
implementations or infer clock quality.</t>
<t>Since the transmit timestamp is essential for offset calculation
(Section 7.1), it cannot be randomized without degrading
synchronization precision. Clients concerned with privacy
<bcp14>SHOULD</bcp14> rely on network-layer protections (e.g., source
address anonymization) rather than timestamp manipulation.</t>
</section>

<section anchor="replay-attacks"><name>Replay Attacks</name>
<t>An attacker who captures a valid OITP response can replay it to
a client at a later time. The origin timestamp check
(Section 10.4) mitigates this: the replayed response must carry
the correct origin timestamp for the client's current request,
which changes with every query. A replayed response will be
rejected unless the attacker can also observe and match the
client's current transmit timestamp.</t>
</section>

<section anchor="future-authentication"><name>Future Authentication</name>
<t>This specification does not define an authentication mechanism.
A future extension could define authentication extension
fields appended after the base 48-byte packet, similar to NTS
(Network Time Security, <xref target="RFC8915"></xref>) for NTP. Such an extension
could use modern symmetric or asymmetric cryptographic
primitives. Future authentication extensions could take
inspiration from protocols such as Roughtime
<xref target="I-D.ietf-ntp-roughtime"></xref>, which provides cryptographic time
authentication with accountability.</t>
</section>

<section anchor="extension"><name>Extension Mechanism</name>
<t>Implementations <bcp14>MUST</bcp14> send packets of exactly 48 octets for
version 1 of this protocol. Implementations <bcp14>MUST</bcp14> accept and
process packets of at least 48 octets. Implementations <bcp14>MUST</bcp14>
silently discard packets shorter than 48 octets. Implementations
<bcp14>MUST</bcp14> ignore any bytes beyond the 48-byte base packet. Future
versions of OITP could define extension fields appended after the
base header. The version field (Section 6.2) will be incremented
for incompatible changes. Compatible extensions <bcp14>MUST</bcp14> be
designed such that implementations unaware of them can safely
discard the extra bytes. Because Version 1 implementations silently
ignore bytes beyond the 48-octet base packet, an on-path attacker
could strip a future authentication extension by truncating the
packet. Any future authentication mechanism <bcp14>MUST</bcp14> use a new
protocol version number or a mandatory-to-process signaling
mechanism that cannot be silently removed, to prevent downgrade
attacks.</t>
<t>Note: Version negotiation (allowing a client to discover and
prefer the highest mutually-supported version) is not defined
in this specification and is deferred to a future document.
Version 1 implementations <bcp14>MUST</bcp14> silently discard requests
and responses carrying an unrecognized version number.</t>
</section>
</section>

<section anchor="iana-considerations"><name>IANA Considerations</name>

<section anchor="port-number"><name>Port Number</name>
<t>This document requests the assignment of port number 8640
(UDP and TCP) for the Open Internet Time Protocol. The port
number 8640 references the fundamental constant of the decimal
time system: 86400 seconds per day divided into 1000 beats of
86.4 seconds each. The port falls within the User Port range
(1024-49151) and is currently unassigned in the IANA Service
Name and Transport Protocol Port Number Registry.</t>
<table>
<thead>
<tr>
<th>Field</th>
<th>Value</th>
</tr>
</thead>

<tbody>
<tr>
<td>Service Name</td>
<td>oitp</td>
</tr>

<tr>
<td>Port Number</td>
<td>8640</td>
</tr>

<tr>
<td>Transport Protocol</td>
<td>UDP, TCP</td>
</tr>

<tr>
<td>Description</td>
<td>Open Internet Time Protocol</td>
</tr>

<tr>
<td>Reference</td>
<td>This document</td>
</tr>
</tbody>
</table><t>TCP registration is requested because the <bcp14>OPTIONAL</bcp14> HTTP
time interface (Appendix D) operates on TCP using the same
port number. Registering both transports prevents future port
conflicts and follows the recommendation of <xref target="RFC6335"></xref>
Section 7.2 to register both UDP and TCP when either is used.</t>
</section>

<section anchor="reference-id-registry"><name>Reference ID Registry</name>
<t>This document requests IANA create an &quot;OITP Reference Identifier&quot;
registry. Registry entries consist of:</t>

<ul spacing="compact">
<li><strong>Value</strong>: a 4-octet ASCII string (right-padded with NUL bytes
(0x00) if shorter), encoded as a 32-bit big-endian integer</li>
<li><strong>Stratum</strong>: the stratum value(s) for which this entry applies</li>
<li><strong>Meaning</strong>: description of the reference source or condition</li>
<li><strong>Reference</strong>: the document defining this entry</li>
</ul>
<t>The registration policy is Specification Required <xref target="RFC8126"></xref>.
IANA is requested to designate one or more experts for this
registry.</t>
<t>Initial entries:</t>
<table>
<thead>
<tr>
<th>Value</th>
<th>Stratum</th>
<th>Meaning</th>
</tr>
</thead>

<tbody>
<tr>
<td><tt>NTP\0</tt></td>
<td>1</td>
<td>Time derived from an NTP source</td>
</tr>

<tr>
<td><tt>GPS\0</tt></td>
<td>0</td>
<td>GPS or GNSS hardware reference clock</td>
</tr>

<tr>
<td><tt>PPS\0</tt></td>
<td>0</td>
<td>Pulse-per-second hardware reference</td>
</tr>

<tr>
<td><tt>RATE</tt></td>
<td>3</td>
<td>KoD: rate limit exceeded</td>
</tr>

<tr>
<td><tt>DENY</tt></td>
<td>3</td>
<td>KoD: access denied</td>
</tr>

<tr>
<td><tt>RSTR</tt></td>
<td>3</td>
<td>KoD: access restricted</td>
</tr>

<tr>
<td><tt>STEP</tt></td>
<td>3</td>
<td>KoD: server stepping clock</td>
</tr>
</tbody>
</table></section>
</section>

</middle>

<back>
<references><name>References</name>
<references><name>Normative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5234.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6335.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.768.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8126.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
</references>
<references><name>Informative References</name>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml3/reference.I-D.ietf-ntp-roughtime.xml"/>
<reference anchor="IEEE1588" target="">
  <front>
    <title>IEEE Standard for a Precision Clock Synchronization Protocol for Networked Measurement and Control Systems</title>
    <author>
      <organization>IEEE</organization>
    </author>
    <date year="2019"></date>
  </front>
  <refcontent>IEEE Std 1588-2019</refcontent>
</reference>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5905.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6234.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.791.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8200.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8259.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8915.xml"/>
<xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9110.xml"/>
</references>
</references>

<section anchor="acknowledgments"><name>Acknowledgments</name>
<t>OITP's design borrows from the Network Time Protocol, originally
developed by David L. Mills. The four-timestamp exchange,
stratum hierarchy, and Kiss-o'-Death mechanism all derive from
NTP's architecture.</t>
</section>

<section anchor="reference-implementation-notes"><name>Reference Implementation Notes</name>
<t>The reference implementation, named Beatnik, is maintained at:
<eref target="https://oitp.net/download.html">https://oitp.net/download.html</eref></t>
<t>Beatnik provides:</t>

<ul spacing="compact">
<li>beatnik serve: Run an OITP server (stratum 0 with GPS/PPS,
or stratum 1 with NTP derivation)</li>
<li>beatnik query &lt;server&gt;: Single query with offset/delay
display</li>
<li>beatnik sync &lt;server&gt;: Daemon mode with clock discipline</li>
<li>beatnik now: Display current decimal time</li>
</ul>
<t>Beatnik targets a single static binary with zero external
dependencies beyond the Zig standard library.</t>
</section>

<section anchor="example-exchange"><name>Example Exchange</name>
<t>Client (192.0.2.1) queries server (198.51.100.1) at
approximately @248.500:</t>

<sourcecode type="ascii-art"><![CDATA[Client Request:
  Version: 1, Mode: 2 (Full Client), Leap: 0, Stratum: 3
  Precision: -10, Poll: 0
  Root Delay: 0, Root Dispersion: 0
  Reference ID: 0x00000000
  Reference Timestamp:  0x0000000000000000
  Origin Timestamp:     0x0000000000000000
  Receive Timestamp:    0x0000000000000000
  Transmit Timestamp:   0x0027103E20000000
    (Day 10000, Beat 248, Fraction 0.500)

    Timestamp encoding breakdown:
      Day 10000  = 0x002710 -> bits 63-40
      Beat 248   = 0x0F8    -> bits 39-30
                               (10 bits: 00_1111_1000)
      Frac 0.500 = 0x20000000 -> bits 29-0
                               (0.5 * 2^30 = 536870912)
      Raw: (0x002710 << 40) | (0x0F8 << 30)
           | 0x20000000
         = 0x0027103E20000000

Server Response:
  Version: 1, Mode: 3 (Server), Leap: 0, Stratum: 1
  Precision: -14, Poll: 64
  Root Delay: 0x00000083
  Root Dispersion: 0x00000041
  Reference ID: "NTP\0"
  Reference Timestamp:  0x0027103E00000000
                        (Day 10000, Beat 248.000)
  Origin Timestamp:     0x0027103E20000000
                        (copied from request)
  Receive Timestamp:    0x0027103E20040000
                        (Beat 248.500244)
  Transmit Timestamp:   0x0027103E20048000
                        (Beat 248.500275)

Client receives response, records T4:
  T4 (local clock):     0x0027103E20088000
                        (Beat 248.500519)

  T1 = 0x0027103E20000000  (client transmit)
  T2 = 0x0027103E20040000  (server receive)
  T3 = 0x0027103E20048000  (server transmit)
  T4 = 0x0027103E20088000  (client receive)

  Linearization (Section 7.1):
  All four timestamps share Day 10000 and Beat 248, so
  linearization is a no-op in this example. In general,
  toLinear(ts) = day*1000*2^30 + beat*2^30 + frac.

  T2 - T1 = 0x40000 = 262144 raw units
  T3 - T4 = -0x40000 = -262144 raw units

  offset = (262144 + (-262144)) / 2
         = 0 (symmetric path, clocks agree)
  delay  = (T4-T1) - (T3-T2)
         = 0x88000 - 0x8000
         = 524288 raw units
         = 524288 / 1073741.824
         ~ 0.488 millibeats (~42 ms round-trip)
]]></sourcecode>
</section>

<section anchor="http-time-interface"><name>HTTP Time Interface</name>
<t>This appendix is informative. It describes a convention for
HTTP-based time queries and is not part of the normative
protocol specification.</t>
<t>OITP servers may expose an HTTP <xref target="RFC9110"></xref> interface
alongside the UDP protocol to provide low-friction access to the
current decimal time. This interface is optional and
informational; it does not participate in the synchronization
protocol.</t>

<section anchor="endpoints"><name>Endpoints</name>
<t><strong>GET /time</strong> (or the root path): Returns the current beat time as
plain text in the format <tt>@HHH.SSS\n</tt> where HHH is the
zero-padded three-digit integer beat (000-999) and SSS is the
zero-padded three-digit millibeat (000-999), truncated (not
rounded) from the internal 30-bit fraction. The trailing
newline is required (Content-Type: text/plain).</t>
<t><strong>GET /json</strong>: Returns a JSON <xref target="RFC8259"></xref> object with the
following fields (Content-Type: application/json):</t>

<sourcecode type="json"><![CDATA[{
  "timestamp": "2026.03.10@248.573",
  "time": "@248.573",
  "day": 10000,
  "beat": 248,
  "millibeat": 573,
  "date": "2026.03.10"
}
]]></sourcecode>
<table>
<thead>
<tr>
<th>Field</th>
<th>Type</th>
<th>Description</th>
</tr>
</thead>

<tbody>
<tr>
<td>timestamp</td>
<td>string</td>
<td>Combined date-time (Section 3.5)</td>
</tr>

<tr>
<td>time</td>
<td>string</td>
<td>Formatted beat time (@HHH.SSS)</td>
</tr>

<tr>
<td>day</td>
<td>integer</td>
<td>OITP day number since epoch</td>
</tr>

<tr>
<td>beat</td>
<td>integer</td>
<td>Integer beat (0-999)</td>
</tr>

<tr>
<td>millibeat</td>
<td>integer</td>
<td>Fractional millibeats (0-999)</td>
</tr>

<tr>
<td>date</td>
<td>string</td>
<td>Calendar date at UTC+1</td>
</tr>
</tbody>
</table></section>

<section anchor="operational-notes"><name>Operational Notes</name>

<ul spacing="compact">
<li>The HTTP interface should include
<tt>Access-Control-Allow-Origin: *</tt> to enable browser-based
clients.</li>
<li>The HTTP interface runs on TCP port 8640, sharing the same
port number as the UDP protocol. Since UDP and TCP are
distinct transport protocols, no conflict arises.</li>
<li>Responses reflect the server's current time at the moment of
response generation, without any offset or delay compensation.</li>
<li>Rate limiting should be applied independently from the UDP
rate limiter.</li>
</ul>
</section>
</section>

</back>

</rfc>
