Skip to content
academia.sh

Lesson 16 / 16

Time and Management Protocols

Time synchronization's one assumption and the round's asymmetry: if the outbound and return delay are not equal, clock offset follows directly from that gap and the protocol has no field in its hands to see it; alongside this, the round and decision balance of the polling and the notifying management model.

Contents

The previous two lessons measured what the intermediary could read from a message. Reading had a quiet precondition: a cache’s freshness lifetime, a certificate’s validity interval, a mail’s Date: field — all of them assume the two ends’ clocks are close to each other.

The protocol that brings clocks close together is the purest example of the course’s measurement axis: it has four timestamps in hand and estimates clock offset in a single round. The estimate rests on a single assumption — that the outbound delay equals the return delay. This lesson’s question is what happens when the assumption does not hold, and whether the protocol can see that it does not.

  • OP51. The setup is M04/K03’s shared setup: station.example and a time source. No real time server’s name is written, no real network call is made.
  • OP52. The set measured is 40 rounds. The oracle is known because we produced the setup ourselves: the server’s clock is 12.500 ms ahead of the client, and the outbound path is 4.000 ms longer than the return path. The paths jitter round to round, but the asymmetry is systematic; the protocol does not see these two numbers, it only sees the four timestamps.
  • OP53. On the device management side the set is 40 devices and a 20-tick window. Every device’s state changes once inside the window; the measure is whether the manager knows the correct state by the end of the window.
  • OP54. The System Administration course in M03 measured clock drift’s symptoms in logs; that measurement is not repeated. The measure here is synchronization’s own error.

Four Timestamps, Two Computations

Clock synchronization is done with a single exchange. Each end writes its own clock the moment it touches the message; these are the four timestamps NTP carries.

client                                    server (time source)
   |                                        |
   |-- t1: moment the client sends -------->|
   |                                        t2: moment the server receives
   |                                        t3: moment the server sends
   |<---------------------------------------|
   t4: moment the client receives           |

   t1, t4  read on the client's clock
   t2, t3  read on the server's clock

This dump is not run; it is the taught form. Two computations follow from these four numbers:

θ^=(t2t1)+(t3t4)2δ=(t4t1)(t3t2)\hat{\theta} = \frac{(t_2 - t_1) + (t_3 - t_4)}{2} \qquad \delta = (t_4 - t_1) - (t_3 - t_2)

The first is the clock offset estimate, the second is the round’s total delay. The second computation is entirely independent of the clock offset: because the offset enters once as a plus and once as a minus, it cancels out. The first computation, instead, halves the sum by assuming the outbound delay equals the return delay.

If the assumption does not hold, the error has a closed form. Let the outbound delay be dgd_g and the return delay ddd_d; the gap between the estimate and the true offset is exactly:

θ^θ=dgdd2\hat{\theta} - \theta = \frac{d_g - d_d}{2}

So the error does not come from noise, it comes from the round’s asymmetry, and it equals half the gap.

The Measurement: Asymmetry

SEED = 20260809
OFFSET = 0.0125          # oracle: server clock is this far ahead of the client (s)
ASYMMETRY = 0.0040       # oracle: the outbound path is this much longer than the return path (s)


def generator(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def rounds(count=40, seed=SEED):
    r, out = generator(seed), []
    for i in range(count):
        common = 0.0150 + 0.0001 * r(9)
        outbound = common + ASYMMETRY / 2 + 0.00002 * r(7)
        inbound = common - ASYMMETRY / 2 + 0.00002 * r(7)
        t1 = 100.0 + i
        t2 = t1 + outbound + OFFSET
        t3 = t2 + 0.0002
        t4 = t3 + inbound - OFFSET
        out.append((t1, t2, t3, t4))
    return out


def estimate(d):
    t1, t2, t3, t4 = d
    return ((t2 - t1) + (t3 - t4)) / 2, (t4 - t1) - (t3 - t2)


sample = rounds()
estimates = [estimate(d) for d in sample]
errors = [s - OFFSET for s, _ in estimates]
least_delay = min(estimates, key=lambda x: x[1])
print(f"round {len(sample)}, true offset {OFFSET * 1000:.3f} ms, "
      f"asymmetry {ASYMMETRY * 1000:.3f} ms")
print(f"estimate average {sum(s for s, _ in estimates) / len(sample) * 1000:.4f} ms")
print(f"error: average {sum(errors) / len(errors) * 1000:.4f}, "
      f"smallest {min(errors) * 1000:.4f}, largest {max(errors) * 1000:.4f} ms")
print(f"half the asymmetry {ASYMMETRY / 2 * 1000:.4f} ms")
print(f"least-delay round: estimate {least_delay[0] * 1000:.4f} ms, "
      f"error {(least_delay[0] - OFFSET) * 1000:.4f} ms")

diff = 0.0
for t1, t2, t3, t4 in sample:
    offset_twin = ((t2 - t1) + (t3 - t4)) / 2
    half = ((t4 - t1) - (t3 - t2)) / 2
    diff = max(diff, abs(t1 + half + offset_twin - t2),
               abs(t3 + half - offset_twin - t4))
print(f"largest difference among the timestamps the observational twin produces: {diff:.3e} s")
round 40, true offset 12.500 ms, asymmetry 4.000 ms
estimate average 14.4917 ms
error: average 1.9917, smallest 1.9400, largest 2.0400 ms
half the asymmetry 2.0000 ms
least-delay round: estimate 14.4700 ms, error 1.9700 ms
largest difference among the timestamps the observational twin produces: 2.842e-14 s

What the Protocol Cannot See

The estimates average 14.4917 ms; the true offset is 12.500 ms; the error is 1.9917 ms, and half the asymmetry is 2.0000 ms. The smallest error is 1.9400, the largest 2.0400 — all forty rounds sit inside a narrow band around two milliseconds. This is the proof the error is not random: if it were noise, the errors would scatter on both sides of zero.

The standard improvement is measuring many rounds and picking the least-delay one; the round with the least queuing is considered the cleanest measurement. When that round is picked, the error drops from 1.9917 to 1.9700 ms. Filtering cuts noise, it does not cut asymmetry — because asymmetry is not noise, it is a property of the path itself.

The last line is the crux. A second world producing the same four timestamps was constructed: in it, the path is symmetric and the server’s clock really is 14.4917 ms ahead. The two worlds’ timestamps agree down to 2.842×10142.842 \times 10^{-14} seconds — which is the floating-point number’s own resolution. The four timestamps cannot tell two different truths apart; the estimate is not miscomputed, in the twin world it is exactly correct.

Because there is no field to show the asymmetry, this is not even a round debt: no matter how many rounds are spent, it does not close. Closing it lies outside the protocol — knowing from another source that the path is symmetric, or placing the timestamps before the path is entered. The course’s fourth reading takes its sharpest form here: nothing verifies what is written in the message; here, what is written in the message is true, and it still is not enough.

Polling Against Notifying

Device management asks the same axis with a different question: how does a manager learn the state of forty devices? SNMP defines both models at once. In the polling model the manager asks at regular intervals, and every question is a round trip. In the notifying model the device reports the change itself when it happens; the unacknowledged form does not even cost a round, the acknowledged form costs one confirmation round.

SEED = 20260809
DEVICES, WINDOW = 40, 20


def generator(seed):
    d = seed % 2147483646 + 1

    def r(n):
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def events():
    r = generator(SEED + 1)
    return [{"device": i + 1, "tick": 1 + r(19), "dropped": r(9) == 0}
            for i in range(DEVICES)]


def poll(interval):
    round_count = correct = delay = 0
    for e in events():
        round_count += WINDOW // interval
        seen = ((e["tick"] - 1) // interval + 1) * interval
        if seen <= WINDOW:
            correct += 1
            delay += seen - e["tick"]
    return round_count, correct, delay / max(correct, 1)


def notify(acked):
    dropped = sum(x["dropped"] for x in events())
    return (DEVICES, DEVICES, 0.0) if acked else (0, DEVICES - dropped, 0.0)


print(f"{'model':<22s} {'round':>5s} {'correct':>6s} {'wrong':>7s} {'delay':>9s}")
for name, (t, d, g) in (("poll, interval 5", poll(5)),
                         ("poll, interval 10", poll(10)),
                         ("poll, interval 20", poll(20)),
                         ("notify, unacked", notify(False)),
                         ("notify, acked", notify(True))):
    print(f"{name:<22s} {t:5d} {d:6d} {DEVICES - d:7d} {g:6.2f} tick")

print(f"\ndevices {DEVICES}, window {WINDOW} ticks, "
      f"notifications dropped in transit {sum(x['dropped'] for x in events())}")
model                  round correct   wrong     delay
poll, interval 5         160     40       0   2.08 tick
poll, interval 10         80     40       0   4.95 tick
poll, interval 20         40     40       0  10.95 tick
notify, unacked            0     34       6   0.00 tick
notify, acked             40     40       0   0.00 tick

devices 40, window 20 ticks, notifications dropped in transit 6

In the polling model, round count and detection lag trade off directly: as the interval rises from five to twenty, rounds fall from 160 to 40 and delay rises from 2.08 ticks to 10.95. The correct decision count is forty in all three — polling learns late, but it does not learn wrong.

The notifying model breaks the trade-off. Its acknowledged form gives the accuracy of the most frequent polling with 40 rounds instead of 160, and zero delay. The unacknowledged form drives round cost to zero and produces a wrong decision on six devices. What matters about these six wrong decisions is this: the manager cannot tell silence apart from no change, and does not know it is wrong. The acknowledgment round buys exactly this distinction — this lesson’s form of the course’s rule is: an unavailable decision is a round debt, and when the debt goes unpaid, an assumption takes the decision’s place.

Summary

  • Clock synchronization derives two computations from four timestamps: the delay computation is independent of clock offset, while the offset estimate assumes the outbound and return delays are equal.
  • If the assumption does not hold, the error is not random: across 40 rounds the true offset is 12.500 ms while the estimates average 14.4917 ms, an error of 1.9917 ms — exactly half the asymmetry. Picking the least-delay round only brings the error down to 1.9700 ms; filtering removes noise, not asymmetry.
  • A symmetric twin world producing the same four timestamps can be constructed; the timestamps agree down to floating-point resolution, so the protocol has no field to see the asymmetry, and this gap does not close by spending rounds.
  • In management, the polling model trades rounds for delay (160 rounds and 2.08 ticks versus 40 rounds and 10.95 ticks); the acknowledged notifying model gives the same accuracy with 40 rounds and zero delay.
  • With unacknowledged notification the round count is zero but six devices come out wrong; the manager cannot tell silence apart from no change, and the acknowledgment round buys exactly that distinction.

Course Wrap-Up

lesson decision measured taken without a round trip wrong or unavailable
HTTP 01 · Methods repeatable, storable from the method 195/200 correct, 0 round 5 wrong: 2 missing flag, 3 validator
HTTP 02 · Status codes caching and retry from the first digit line regime 309 correct, 4004 bytes 11 wrong; the body’s 6 decisions cost 673.3 bytes/decision
HTTP 03 · Headers and negotiation the split between the resolved field and the carried field 195 correct; with the full flag 13/13 correct copy 5 wrong; 9 wrong copies without the flag; 2960 bytes zero decisions
HTTP 04 · Cookies the scope rule and who receives the cached response 195 correct; scopes carry 11, 24, 40 messages 2 wrong; with the flag wrapped, 80 decisions move to the endpoints, 40 rounds
HTTP 05 · Versions what framing buys the intermediary same 195 correct, 440 bytes instead of 3724 189 unavailable in a context-less intermediary, 39 rounds
HTTP 06 · Caching the validator’s resolution tag regime 195 correct, 9 of 12 stale copies caught the tag misses staleness in 3 copies; asking every time costs 40 rounds
TLS 01 · Symmetric and asymmetric the round cost of the role 0.025 round/exchange on a single persistent connection 1.000 round on setup for every message
TLS 02 · Certificate chain local verification of the chain 33/40 correct, 0 round trip; still 0 even as the chain lengthens 7 wrong, all from revocation; asking costs 111 rounds, same table
TLS 03 · Handshake the round budget and the decision the envelope charges 40 decisions inside the envelope, none wrong 160 unavailable, 40 rounds; bytes from 3847 to 920
TLS 04 · Certificate life cycle revocation being a round debt a client that never asks pays 0 rounds; all three options in between also have 0 round per exchange never asking gives 22.02/40 false acceptance; asking on every exchange costs 120 rounds, 0 wrong
OTHER 01 · WebSocket the upgrade being a regime change the upgrade request gives 2 of 5 decisions 200 unavailable in the tunnel, 40 rounds, 0 wrong
OTHER 02 · Server stream the stream staying inside HTTP 1 decision point, 5 decisions, 87 bytes 195 unavailable, 39 rounds; 73 wrong across scope
OTHER 03 · Polling the round cost per decision 0.200 round/decision in all four approaches spent round plus round debt sums to 40 in all of them
OTHER 04 · File transfer the match between the control and data channel single channel open 195 correct, 0 round 8 wrong on two channels, 3 from the split; data channel wrapped costs 40 rounds
OTHER 05 · Mail the split between the mail envelope and the header 60 correct with the envelope, 140 with the header, 180 with the key 7 wrong from forwarding; unavailable drops from 133 to 13
OTHER 06 · Time and management round asymmetry and the management model acked notification 40 correct, 40 rounds, 0 delay error 1.9917 ms, does not close by adding rounds; 6 wrong when unacked

Every number in the table is read from that row’s own lesson’s measurement; no row was filled in from the shared setup.

The course had one sentence, and sixteen lessons measured it: whatever is written in the message, the intermediary knows it; whatever is not written, it does not know; and whatever is written wrong, it knows wrong. The measure stayed the same throughout — a protocol’s number is not the fields it carries but how many decisions it lets the receiving side take without an extra round trip — and every unavailable decision was written down as a round debt.

Three results stand at the close. Adding a field does not buy a decision: bytes grow, the decision table does not change; what buys the gain is not the byte itself, it is being a field whose name and meaning are fixed. The cost of hiding is not a defect, it is a choice: once the transport envelope closes, the intermediary loses most of the decisions, but there is no wrong decision among what remains. And nothing verifies a decision: the protocol trusts the declaration, because the declaration is all it has. The last lesson showed the most extreme form of this — there, the declaration was true and it still was not enough, because what was missing was not a field but a world.

The intermediary that took all these decisions read the message throughout the course. The next course, Switching and Routing, drops down one layer and looks at the device standing there: a switch deciding which port to send a frame out of, a router deciding which neighbor to hand a packet to, and which table each of these decisions is read from. The measure shifts too — here the intermediary was counted by what it knew; there the device will be counted by how correctly it forwards. From the intermediary that takes the decision, to the device that forwards the packet.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ Esc navigate · open · close