ntp

    NTP packet analysis, authenticated UDP, clock filters and source consensus

    Download zip
    Author
    Version
    0.4.0
    License
    MIT
    Last updated
    5 hours ago
    Downloads
    1

    #可执行 API 示例

    MoonBit API 示例随两个后端测试运行。Node UDP、认证和浏览器宿主另有集成检查。

    ///|
    test "empty measurement selection" {
    assert_true(@ntp.lowest_delay([]) is None)
    }

    ///|
    test "eight-stage clock filter example" {
    let filter = @ntp.ClockFilter::new()
    for i = 1; i <= 8; i = i + 1 {
    let now = i.to_double()
    filter.begin_poll(now)
    ignore(
    filter.observe({
    offset_seconds: 0.01,
    delay_seconds: 0.02,
    dispersion_seconds: 0.000001,
    received_at: now,
    }),
    )
    }
    let result = filter.estimate(8.0).unwrap()
    assert_eq(result.valid_samples, 8)
    assert_eq(result.reach, 255)
    assert_eq(result.offset_seconds, 0.01)
    assert_true(!result.fresh)
    }

    过滤器返回统计估计;fresh=false 表示最佳样本没有更新。这里不修改操作系统时钟,也不实现完整 NTP daemon。

    NtpError

    pub suberror NtpError {
    Invalid(String)
    } derive(
    Debug
    )

    ClockFilter

    pub struct ClockFilter {
    entries : Array[FilterSample]
    precision : Double
    tolerance : Double
    last_time : Double?
    accepted : FilterEstimate?
    reach : Int
    } derive(
    Debug
    )

    ClockFilter::begin_poll

    fn ClockFilter::begin_poll(self : ClockFilter, now : Double) -> Unit raise NtpError

    Call once when beginning a poll. After three missed polls a dummy is shifted into the filter, so eight subsequent missing polls eventually replace it.

    ClockFilter::estimate

    fn ClockFilter::estimate(self : ClockFilter, now : Double) -> FilterEstimate? raise NtpError

    Snapshot statistics age without mutating stage arrival times. The fresh flag indicates whether this lowest-delay sample has advanced the accepted epoch.

    ClockFilter::new

    fn ClockFilter::new(precision_seconds? : Double, frequency_tolerance? : Double) -> ClockFilter raise NtpError

    Eight stages, MAXDISP 16 seconds, PHI 15 ppm; no OS clock discipline.

    ClockFilter::observe

    fn ClockFilter::observe(self : ClockFilter, sample : FilterSample) -> FilterEstimate? raise NtpError

    Consensus

    pub(all) struct Consensus {
    offset_seconds : Double
    jitter_seconds : Double
    selection_jitter_seconds : Double
    interval_low : Double
    interval_high : Double
    system_peer : String
    survivors : Array[String]
    rejected : Array[String]
    } derive(
    Debug
    )

    Datagram

    pub(all) struct Datagram {
    header : Packet
    extensions : Array[ExtensionField]
    mac : Bytes
    } derive(Eq,
    Debug
    )

    Datagram::encode

    fn Datagram::encode(self : Datagram) -> Bytes raise NtpError

    ExtensionField

    pub(all) struct ExtensionField {
    field_type : Int
    value : Bytes
    } derive(Eq,
    Debug
    )

    FilterEstimate

    pub(all) struct FilterEstimate {
    offset_seconds : Double
    delay_seconds : Double
    dispersion_seconds : Double
    jitter_seconds : Double
    selected_at : Double
    evaluated_at : Double
    valid_samples : Int
    reach : Int
    fresh : Bool
    } derive(
    Debug
    )

    FilterEstimate::root_distance

    fn FilterEstimate::root_distance(self : FilterEstimate, root_delay_seconds : Double, root_dispersion_seconds : Double) -> Double raise NtpError

    FilterSample

    pub(all) struct FilterSample {
    offset_seconds : Double
    delay_seconds : Double
    dispersion_seconds : Double
    received_at : Double
    } derive(Eq,
    Debug
    )

    Packet

    pub(all) struct Packet {
    leap : Int
    version : Int
    mode : Int
    stratum : Int
    poll : Int
    precision : Int
    root_delay : Int
    root_dispersion : UInt
    reference_id : UInt
    reference : Timestamp
    origin : Timestamp
    receive : Timestamp
    transmit : Timestamp
    } derive(Eq,
    Debug
    )

    Packet::encode

    fn Packet::encode(self : Packet) -> Bytes raise NtpError

    Packet::kiss_code

    fn Packet::kiss_code(self : Packet) -> String?

    Returns an ASCII kiss-of-death code for stratum-zero replies.

    Packet::reference_string

    fn Packet::reference_string(self : Packet) -> String

    Packet::root_distance

    fn Packet::root_distance(self : Packet, sample : Sample) -> Double

    Single-sample root distance; excludes multi-sample jitter and clock filtering.

    Packet::validate_health

    fn Packet::validate_health(self : Packet) -> Unit raise NtpError

    Check synchronization health in addition to measure's request/time validation. Freshness and peer-distance limits follow beevik/ntp's Validate defaults.

    PeerEstimate

    pub(all) struct PeerEstimate {
    id : String
    offset_seconds : Double
    root_distance_seconds : Double
    jitter_seconds : Double
    stratum : Int
    } derive(Eq,
    Debug
    )

    ResponseInfo

    pub(all) struct ResponseInfo {
    sample : Sample
    minimum_error_seconds : Double
    server_transmit_unix : Double
    reference_unix : Double
    precision_seconds : Double
    poll_seconds : Double
    root_distance_seconds : Double
    reference_string : String
    } derive(
    Debug
    )

    Sample

    pub struct Sample {
    offset_seconds : Double
    delay_seconds : Double
    } derive(
    Debug
    )

    Timestamp

    pub(all) struct Timestamp {
    seconds : UInt
    fraction : UInt
    } derive(Eq,
    Debug
    )

    Timestamp::difference

    fn Timestamp::difference(self : Timestamp, earlier : Timestamp) -> Double

    Modular signed seconds handle era rollover; intervals must be less than 2^31 seconds.

    Timestamp::near_unix

    fn Timestamp::near_unix(self : Timestamp, pivot : Double) -> Double raise NtpError

    Resolve within half an NTP era of a trusted local pivot (pivot in eras 0/1).

    Timestamp::unix_seconds

    fn Timestamp::unix_seconds(self : Timestamp, era : Int) -> Double raise NtpError

    NTP era is supplied by the caller: 0 before 2036 rollover, 1 afterwards.

    Timestamp::zero

    fn Timestamp::zero() -> Timestamp

    analyze

    fn analyze(sent : Timestamp, reply : Packet, arrived : Timestamp, pivot_unix : Double) -> ResponseInfo raise NtpError

    Rich SNTP analysis. A small negative measured network delay is clamped to zero, as in beevik/ntp; strict measure() retains its existing rejection.

    combine_peers

    fn combine_peers(peers : Array[PeerEstimate], minimum? : Int, cluster_minimum? : Int, maximum_distance? : Double) -> Consensus? raise NtpError

    RFC 5905 section 11.2 interval selection, iterative clustering and inverse-root-distance combination. This does not perform clock discipline.

    decode

    fn decode(bytes : Bytes) -> Packet raise NtpError

    decode_datagram

    fn decode_datagram(bytes : Bytes, mac_length? : Int) -> Datagram raise NtpError

    Explicit expected MAC size avoids guessing whether a tail is an extension. Supports no MAC, a 4-byte crypto-NAK, or the common 20/24-byte MAC forms.

    from_unix

    fn from_unix(seconds : Double) -> (Timestamp, Int) raise NtpError

    lowest_delay

    fn lowest_delay(samples : Array[Sample]) -> Sample?

    Choose the valid measurement with minimum observed network delay. This is a single-stage selector, not the complete RFC clock-filter algorithm.

    measure

    fn measure(sent : Timestamp, reply : Packet, arrived : Timestamp) -> Sample raise NtpError

    Verifies basic server reply and computes RFC four-timestamp estimates. Caller supplies the original request timestamp and local receipt timestamp.

    request

    fn request(transmit : Timestamp) -> Packet