mirror of
https://github.com/soypat/lneto.git
synced 2026-08-24 08:29:08 +00:00
feat(ntp): add two-exchange client, server handler, and extension fields (#101)
Implement full two-exchange NTP client state machine (RFC 5905 §8). First exchange stores offset/RTT, second averages both for improved accuracy. Client places T1 in TransmitTime per RFC 5905 §8; server echoes it as OriginTime in the response. Add NTP extension field codec (RFC 7822) with NextExtField iterator and AppendExtField builder. Add NTS extension type constants from RFC 8915. Add NTP Server (StackNode) that receives client requests via Demux and builds server responses via Encapsulate with configurable stratum, precision, reference ID, and pending request queue. Add Frame accessor methods: RawData(), ExtensionFields(), ValidateSize(), and Timestamp.Uint64(). Add ntp-client and ntp-server example programs with CalculateSystemPrecision, retry limits, and backoff. Use internal.LogAttrs pattern for non-allocating structured logging in the client, matching the tcp/debug.go convention. Generated with Claude assistance. Signed-off-by: Marvin Drees <marvin.drees@9elements.com>
This commit is contained in:
+32
@@ -131,6 +131,34 @@ func (frm Frame) SetTransmitTime(rt Timestamp) {
|
||||
rt.Put(frm.buf[40:48])
|
||||
}
|
||||
|
||||
// RawData returns the underlying byte slice for the entire NTP packet.
|
||||
func (frm Frame) RawData() []byte { return frm.buf }
|
||||
|
||||
// ExtensionFields returns the extension fields area of the NTP packet (all
|
||||
// bytes following the fixed 48-byte NTP header). The RFC calls these
|
||||
// "extension fields" (RFC 7822 §2).
|
||||
func (frm Frame) ExtensionFields() []byte {
|
||||
return frm.buf[SizeHeader:]
|
||||
}
|
||||
|
||||
// ValidateSize checks that the NTP header is complete and that any extension
|
||||
// fields are well-formed with valid lengths.
|
||||
func (frm Frame) ValidateSize(v *lneto.Validator) {
|
||||
if len(frm.buf) < SizeHeader {
|
||||
v.AddError(lneto.ErrTruncatedFrame)
|
||||
return
|
||||
}
|
||||
buf := frm.ExtensionFields()
|
||||
for len(buf) > 0 {
|
||||
_, n, err := NextExtField(buf)
|
||||
if err != nil {
|
||||
v.AddError(err)
|
||||
return
|
||||
}
|
||||
buf = buf[n:]
|
||||
}
|
||||
}
|
||||
|
||||
// ClearHeader zeros out the header contents.
|
||||
func (frm Frame) ClearHeader() {
|
||||
for i := range frm.buf[:SizeHeader] {
|
||||
@@ -216,6 +244,10 @@ func (t Timestamp) Seconds() uint32 { return t.sec }
|
||||
|
||||
func (t Timestamp) Fractions() uint32 { return t.fra }
|
||||
|
||||
// Uint64 returns the full 64-bit NTP timestamp with seconds in the upper 32
|
||||
// bits and fractions in the lower 32 bits. Suitable for logging and encoding.
|
||||
func (t Timestamp) Uint64() uint64 { return uint64(t.sec)<<32 | uint64(t.fra) }
|
||||
|
||||
func (t Short) Seconds() uint16 { return uint16(t >> 16) }
|
||||
func (t Short) Fractions() uint16 { return uint16(t) }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user