lib/quic/src/sim/policy.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 pub const ValidationError = error{
4 InvalidDelayBounds,
5 InvalidPermille,
6 MtuExceedsCapacity,
7 };
8
9 const PolicyValidationError = ValidationError;
10
11 /// Impairments one direction of a simulated link applies, so a test builds one per direction and
12 /// hands both to the link at startup. The link consults the policy on every send in that direction,
13 /// and the two directions carry their own values. The default policy is a clean path: no loss, no
14 /// duplication, no reordering, no delay, and the caller's full datagram capacity.
15 pub const Policy = struct {
16 /// The chance out of one thousand, or permille, that the link drops a datagram it has accepted,
17 /// so a test makes the link throw datagrams away. A dropped datagram raises the sent count and
18 /// the lost count together.
19 loss_permille: u16 = 0,
20 /// The chance out of one thousand that the link queues a second copy of a datagram, so a test
21 /// makes the peer see a datagram twice. The two copies draw their delivery delays separately.
22 duplicate_permille: u16 = 0,
23 /// The chance out of one thousand that a newly queued copy trades delivery times with the most
24 /// recently queued one, so a test makes datagrams arrive out of order. The swap covers the
25 /// delivery time and the send order together, so the two copies exchange places in the queue.
26 reorder_permille: u16 = 0,
27 /// The smallest delivery delay in nanoseconds, which is itself a possible draw, so a test gives
28 /// the path a floor on its latency.
29 delay_min_ns: u64 = 0,
30 /// The largest delivery delay in nanoseconds, which is itself a possible draw, so a test gives
31 /// the path a ceiling on its latency. Each datagram takes a delay drawn uniformly between the
32 /// two bounds.
33 delay_max_ns: u64 = 0,
34 /// The largest datagram in bytes the link will accept in this direction, or MTU, so a test
35 /// makes the path refuse datagrams above a chosen size. Zero selects the caller's datagram
36 /// capacity. A larger send is refused with `Oversize` and counted.
37 mtu: u16 = 0,
38
39 pub const ValidationError: type = PolicyValidationError;
40
41 /// Checks one policy against the caller's datagram capacity for the link at startup so a
42 /// nonsensical policy fails before any datagram moves. A loss, duplication, or reordering
43 /// chance above one thousand gives `InvalidPermille`. A smallest delay above the largest gives
44 /// `InvalidDelayBounds`. An MTU above the caller's datagram capacity gives
45 /// `MtuExceedsCapacity`.
46 pub fn validate(self: Policy, datagram_capacity: u16) PolicyValidationError!void {
47 if (self.loss_permille > 1000) return error.InvalidPermille;
48 if (self.duplicate_permille > 1000) return error.InvalidPermille;
49 if (self.reorder_permille > 1000) return error.InvalidPermille;
50 if (self.delay_min_ns > self.delay_max_ns) return error.InvalidDelayBounds;
51 if (self.mtu > datagram_capacity) return error.MtuExceedsCapacity;
52 std.debug.assert(self.loss_permille <= 1000);
53 std.debug.assert(self.duplicate_permille <= 1000);
54 std.debug.assert(self.reorder_permille <= 1000);
55 std.debug.assert(self.delay_min_ns <= self.delay_max_ns);
56 std.debug.assert(self.mtu <= datagram_capacity);
57 }
58
59 /// Returns the policy's own MTU, or the caller's datagram capacity when the policy leaves it at
60 /// zero, so the link gets the size it must compare against on every send. The result is at most
61 /// the caller's datagram capacity.
62 pub fn effectiveMtu(self: Policy, datagram_capacity: u16) u16 {
63 std.debug.assert(self.mtu <= datagram_capacity);
64 const result = if (self.mtu == 0) datagram_capacity else self.mtu;
65 std.debug.assert(result <= datagram_capacity);
66 return result;
67 }
68 };
69
70 test "zero policy admits the caller datagram capacity" {
71 const policy = Policy{};
72 try policy.validate(1200);
73 try std.testing.expectEqual(@as(u16, 1200), policy.effectiveMtu(1200));
74 }
75
76 test "permille fields admit 1000 and reject 1001" {
77 try (Policy{
78 .loss_permille = 1000,
79 .duplicate_permille = 1000,
80 .reorder_permille = 1000,
81 }).validate(1);
82 try std.testing.expectError(
83 error.InvalidPermille,
84 (Policy{ .loss_permille = 1001 }).validate(1),
85 );
86 try std.testing.expectError(
87 error.InvalidPermille,
88 (Policy{ .duplicate_permille = 1001 }).validate(1),
89 );
90 try std.testing.expectError(
91 error.InvalidPermille,
92 (Policy{ .reorder_permille = 1001 }).validate(1),
93 );
94 }
95
96 test "policy rejects reversed delay bounds and oversized MTU" {
97 try std.testing.expectError(
98 error.InvalidDelayBounds,
99 (Policy{ .delay_min_ns = 2, .delay_max_ns = 1 }).validate(1200),
100 );
101 try std.testing.expectError(
102 error.MtuExceedsCapacity,
103 (Policy{ .mtu = 1201 }).validate(1200),
104 );
105 }