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 }