tiny.quic.sim
Defined in tiny.quic.
API (36)
Actions
Public operations.
Capacity.deriveClock.advance: Moves the clock forward by a given number of nanoseconds so a test puts a chosen span of time between steps.Link.dropNext: Marks which of the next datagrams one endpoint sends are thrown away, by bit position, starting from bit 0 and covering the next 64, so a test loses exactly the datagrams it chooses at the moment it chooses.Link.init: Starts a link once over memory the caller has already built, with one policy for each direction and a seed for the delays and the chances, so the caller receives the value it drives.Link.nextDeliveryAt: Returns the instant at which the earliest pending datagram becomes due at one endpoint, so a test driving a manual clock learns how far time may move before something arrives.Link.receive: Copies the earliest datagram due at one endpoint by a given instant into the caller's buffer and returns its length, so the destination endpoint receives whatever has arrived.Link.send: Takes one datagram from an endpoint at a given instant and applies that direction's policy to it, so the link decides what becomes of the datagram.Link.stats: Returns a copy of the counters for the datagrams one endpoint sent, so a test reads what the path did to those datagrams after a run.Memory.activateMemory.deinitMemory.initMemory.requireFree: Reports whether one direction has room for a given number of further datagrams before the link queues anything, so a full direction is refused before any byte moves.Policy.effectiveMtu: Returns the policy's own MTU, or the caller's datagram capacity when the policy leaves it at zero, so the link gets the size it must compare against on every send.Policy.validate: Checks one policy against the caller's datagram capacity for the link at startup so a nonsensical policy fails before any datagram moves.
Types and contracts
Public types and contracts.
Capacity: The exact storage layout and byte total derived from one set of limits, so a caller learns the exact byte count its storage must have.Capacity.DeriveErrorCapacityErrorClock: A nanosecond counter a test owns and moves forward itself.End: One of the link's two endpoints,aandb, so every call that sends, receives, or reads counters names the endpoint it speaks for.Limits: The two capacities a caller chooses to set how much traffic the link must hold and derive its storage size: the datagrams one direction may hold at once, and the bytes one datagram may carry.Link: A two-ended path that carries opaque datagrams between its endpoints over caller-owned memory, on a schedule the caller can reproduce, so a test holds one and drives both endpoints of a simulated path through it.Link.InitErrorLink.SendErrorMemory: The owner of the queue records and payload bytes a link runs on, so one aligned block handed over by the caller becomes the only memory the link ever writes to.Memory.CapacityMemory.ExhaustionMemory.InitErrorMemory.LimitsMemory.StoragePolicy: Impairments one direction of a simulated link applies, so a test builds one per direction and hands both to the link at startup.Policy.ValidationErrorPolicyErrorStats: The counters one direction of a simulated link keeps.
Values and defaults
Public values and defaults.
Source
Source: lib/quic/src/sim/clock.zig:6
zig
/// A nanosecond counter a test owns and moves forward itself. A test holds a manual clock and/// passes its reading wherever a connection or a link asks for the current time. The clock starts/// at zero. A test alone moves the clock, so the test decides the exact instant of every step.pub const Clock = struct { now_ns: u64 = 0, /// Moves the clock forward by a given number of nanoseconds so a test puts a chosen span of /// time between steps. At the largest representable instant the clock stops there, so the /// reading never goes backward. pub fn advance(self: *Clock, delta_ns: u64) void { const previous = self.now_ns; self.now_ns = std.math.add(u64, self.now_ns, delta_ns) catch std.math.maxInt(u64); std.debug.assert(self.now_ns >= previous); if (self.now_ns != std.math.maxInt(u64)) { std.debug.assert(self.now_ns - previous == delta_ns); } }};Source: lib/quic/src/sim/link.zig:36
zig
/// The exact storage layout and byte total derived from one set of limits, so a caller learns the/// exact byte count its storage must have. The total covers queue records and payload bytes for/// both directions. Limits whose product overflows give `CapacityOverflow`.pub const Capacity = struct { queue_capacity: u32, datagram_capacity: u16, entry_count: usize, entry_bytes: usize, direction_payload_bytes: usize, payload_bytes: usize, storage_bytes: usize, pub const DeriveError: type = CapacityError; pub fn derive(limits: Limits) DeriveError!Capacity { const queue_count: usize = @intCast(limits.queue_capacity); const datagram_bytes: usize = @intCast(limits.datagram_capacity); const entry_count = try alloc_phase.capacity.mul(usize, queue_count, 2); const entry_bytes = try alloc_phase.capacity.mul( usize, entry_count, @sizeOf(Entry), ); const direction_payload_bytes = try alloc_phase.capacity.mul( usize, queue_count, datagram_bytes, ); const payload_bytes = try alloc_phase.capacity.mul( usize, direction_payload_bytes, 2, ); const storage_bytes = try alloc_phase.capacity.add( usize, entry_bytes, payload_bytes, ); return .{ .queue_capacity = limits.queue_capacity, .datagram_capacity = limits.datagram_capacity, .entry_count = entry_count, .entry_bytes = entry_bytes, .direction_payload_bytes = direction_payload_bytes, .payload_bytes = payload_bytes, .storage_bytes = storage_bytes, }; }};Source: lib/quic/src/sim/link.zig:24
zig
pub const CapacityError = error{CapacityOverflow};Source: lib/quic/src/sim/link.zig:11
zig
/// One of the link's two endpoints, `a` and `b`, so every call that sends, receives, or reads/// counters names the endpoint it speaks for. Naming an endpoint picks the queue: a send goes into/// the queue that endpoint owns, and a receive takes from the other one.pub const End = enum(u1) { a, b,};Source: lib/quic/src/sim/link.zig:19
zig
/// The two capacities a caller chooses to set how much traffic the link must hold and derive its/// storage size: the datagrams one direction may hold at once, and the bytes one datagram may/// carry. Both directions get the same capacities.pub const Limits = struct { queue_capacity: u32, datagram_capacity: u16,};Source: lib/quic/src/sim/link.zig:343
zig
/// A two-ended path that carries opaque datagrams between its endpoints over caller-owned memory,/// on a schedule the caller can reproduce, so a test holds one and drives both endpoints of a/// simulated path through it. The link holds one policy per direction, a generator started from the/// caller's seed, the counters, the send order, and the pending drop patterns. The same seed and/// the same sequence of calls give the same outcome every run, so a failing test can be replayed./// The bytes the link carries are opaque to it, so QUIC packets remain outside its view.pub const Link = struct { memory: *Memory, policies: [2]Policy, prng: std.Random.DefaultPrng, counts: [2]Stats = .{ .{}, .{} }, next_sequence: [2]u64 = .{ 0, 0 }, drops: [2]u64 = .{ 0, 0 }, pub const InitError: type = LinkInitError; pub const SendError: type = LinkSendError; /// Starts a link once over memory the caller has already built, with one policy for each /// direction and a seed for the delays and the chances, so the caller receives the value it /// drives. The call moves the memory from its initialization phase into its steady phase, after /// which the partition is fixed. Memory already past initialization gives `StorageNotReady`. /// Limits that differ from the ones the memory was built with give `LimitsMismatch`. Each /// policy is checked against the datagram capacity before anything is sealed, so an invalid /// policy fails here. pub fn init( limits: Limits, storage: *Memory, seed: u64, policy_a_to_b: Policy, policy_b_to_a: Policy, ) InitError!Link { if (storage.phase != .initialization) return error.StorageNotReady; if (storage.capacity.queue_capacity != limits.queue_capacity) { return error.LimitsMismatch; } if (storage.capacity.datagram_capacity != limits.datagram_capacity) { return error.LimitsMismatch; } try policy_a_to_b.validate(limits.datagram_capacity); try policy_b_to_a.validate(limits.datagram_capacity); storage.activate(); return .{ .memory = storage, .policies = .{ policy_a_to_b, policy_b_to_a }, .prng = std.Random.DefaultPrng.init(seed), }; } /// Marks which of the next datagrams one endpoint sends are thrown away, by bit position, /// starting from bit 0 and covering the next 64, so a test loses exactly the datagrams it /// chooses at the moment it chooses. Only datagrams that pass the MTU check are counted against /// the pattern, because the check comes first. A datagram dropped this way counts as sent and /// lost. A marked datagram draws nothing from the seeded generator, so marking a drop leaves /// the rest of the run unchanged. A subsequent call joins the new pattern with the bits still /// pending, and both are counted from the next datagram. pub fn dropNext(self: *Link, from: End, pattern: u64) void { const direction_index = outgoingIndex(from); std.debug.assert(direction_index < self.drops.len); self.drops[direction_index] |= pattern; std.debug.assert(self.drops[direction_index] & pattern == pattern); } /// Takes one datagram from an endpoint at a given instant and applies that direction's policy /// to it, so the link decides what becomes of the datagram. A datagram above the MTU is refused /// with `Oversize` and counted. A datagram the drop pattern names, or that the loss chance /// catches, is counted as sent and lost and stops there. A direction without room for the /// copies is refused with `QueueFull` before anything is queued. Otherwise the bytes are copied /// into the queue with a delivery time drawn from the policy's delay range, and the duplication /// chance may add a second copy with its own delay. The reordering chance trades the new copy's /// delivery time and send order with the most recently queued one. pub fn send(self: *Link, from: End, bytes: []const u8, now_ns: u64) SendError!void { const direction_index = outgoingIndex(from); const policy = self.policies[direction_index]; const mtu: usize = policy.effectiveMtu(self.memory.capacity.datagram_capacity); if (bytes.len > mtu) { increment(&self.counts[direction_index].oversize); return error.Oversize; } const dropped = self.takeDrop(direction_index); if (dropped or self.event(policy.loss_permille)) { increment(&self.counts[direction_index].sent); increment(&self.counts[direction_index].lost); return; } const duplicate = self.event(policy.duplicate_permille); const copies: u2 = if (duplicate) 2 else 1; self.memory.requireFree(from, copies) catch { increment(&self.counts[direction_index].queue_full); return error.QueueFull; }; const reorder = self.event(policy.reorder_permille); const previous = if (reorder) self.previousIndex(direction_index) else null; const first_delay = self.delay(policy); const second_delay = if (duplicate) self.delay(policy) else 0; increment(&self.counts[direction_index].sent); const first = self.enqueue(direction_index, bytes, deliveryAt(now_ns, first_delay)); if (duplicate) { _ = self.enqueue(direction_index, bytes, deliveryAt(now_ns, second_delay)); increment(&self.counts[direction_index].duplicated); } if (previous) |previous_index| { self.swapDelivery(direction_index, first.index, previous_index); increment(&self.counts[direction_index].reordered); } } /// Copies the earliest datagram due at one endpoint by a given instant into the caller's buffer /// and returns its length, so the destination endpoint receives whatever has arrived. The call /// yields null before any datagram becomes due. Ties between equal delivery times go to the /// earlier send order, so a reordered pair keeps a definite order. The caller's buffer holds at /// least the datagram capacity, which debug builds check. The buffer lies outside the link's /// own bytes, because the copy assumes they do not overlap. Taking a datagram frees its queue /// slot for the next send. pub fn receive(self: *Link, at: End, now_ns: u64, out: []u8) ?usize { const direction_index = incomingIndex(at); const entry_index = self.earliestIndex(direction_index) orelse return null; const direction = &self.memory.directions[direction_index]; const entry = &direction.entries[entry_index]; if (entry.delivery_at_ns > now_ns) return null; const length: usize = @intCast(entry.length); std.debug.assert(out.len >= self.memory.capacity.datagram_capacity); const payload = payloadAt( direction, entry_index, self.memory.capacity.datagram_capacity, ); @memcpy(out[0..length], payload[0..length]); self.memory.release(direction_index, entry_index); increment(&self.counts[direction_index].delivered); return length; } /// Returns the instant at which the earliest pending datagram becomes due at one endpoint, so a /// test driving a manual clock learns how far time may move before something arrives. An empty /// queue gives null. pub fn nextDeliveryAt(self: *const Link, at: End) ?u64 { const direction_index = incomingIndex(at); const entry_index = self.earliestIndex(direction_index) orelse return null; return self.memory.directions[direction_index].entries[entry_index].delivery_at_ns; } /// Returns a copy of the counters for the datagrams one endpoint sent, so a test reads what the /// path did to those datagrams after a run. The delivery count belongs to the sending direction /// too, because the receiving endpoint's take is counted against the queue it came from. pub fn stats(self: *const Link, from: End) Stats { return self.counts[outgoingIndex(from)]; } fn event(self: *Link, permille: u16) bool { std.debug.assert(permille <= 1000); if (permille == 0) return false; if (permille == 1000) return true; return self.prng.random().uintLessThan(u16, 1000) < permille; } fn takeDrop(self: *Link, direction_index: usize) bool { std.debug.assert(direction_index < self.drops.len); const pattern = self.drops[direction_index]; self.drops[direction_index] = pattern >> 1; const dropped = pattern & 1 != 0; const remaining = @popCount(self.drops[direction_index]); std.debug.assert(remaining + @intFromBool(dropped) == @popCount(pattern)); return dropped; } fn delay(self: *Link, policy: Policy) u64 { std.debug.assert(policy.delay_min_ns <= policy.delay_max_ns); if (policy.delay_min_ns == policy.delay_max_ns) return policy.delay_min_ns; const span = policy.delay_max_ns - policy.delay_min_ns; const offset = if (span == std.math.maxInt(u64)) self.prng.random().int(u64) else self.prng.random().uintLessThan(u64, span + 1); return policy.delay_min_ns + offset; } fn enqueue( self: *Link, direction_index: usize, bytes: []const u8, delivery_at_ns: u64, ) EntryRef { const acquired = self.memory.acquire(direction_index) catch unreachable; std.debug.assert(bytes.len <= acquired.payload.len); const sequence = self.takeSequence(direction_index); @memcpy(acquired.payload[0..bytes.len], bytes); acquired.entry.* = .{ .delivery_at_ns = delivery_at_ns, .sequence = sequence, .length = @intCast(bytes.len), .occupied = true, }; return acquired; } fn takeSequence(self: *Link, direction_index: usize) u64 { std.debug.assert(direction_index < self.next_sequence.len); std.debug.assert(self.next_sequence[direction_index] < std.math.maxInt(u64)); const sequence = self.next_sequence[direction_index]; self.next_sequence[direction_index] += 1; return sequence; } fn previousIndex(self: *const Link, direction_index: usize) ?usize { const entries = self.memory.directions[direction_index].entries; var selected: ?usize = null; for (entries, 0..) |entry, index| { if (!entry.occupied) continue; if (selected == null) selected = index; if (selected) |current| { if (entry.sequence > entries[current].sequence) selected = index; } } return selected; } fn earliestIndex(self: *const Link, direction_index: usize) ?usize { const entries = self.memory.directions[direction_index].entries; var selected: ?usize = null; for (entries, 0..) |entry, index| { if (!entry.occupied) continue; if (selected == null) selected = index; if (selected) |current| { if (entry.delivery_at_ns < entries[current].delivery_at_ns) selected = index; if (entry.delivery_at_ns != entries[current].delivery_at_ns) continue; if (entry.sequence < entries[current].sequence) selected = index; } } return selected; } fn swapDelivery(self: *Link, direction_index: usize, first: usize, second: usize) void { const entries = self.memory.directions[direction_index].entries; std.debug.assert(first < entries.len); std.debug.assert(second < entries.len); std.mem.swap(u64, &entries[first].delivery_at_ns, &entries[second].delivery_at_ns); std.mem.swap(u64, &entries[first].sequence, &entries[second].sequence); }};Source: lib/quic/src/sim/link.zig:116
zig
/// The owner of the queue records and payload bytes a link runs on, so one aligned block handed/// over by the caller becomes the only memory the link ever writes to. The owner takes one/// caller-provided aligned byte block and partitions it into the two directions' records and/// payload regions. A block whose length differs from the derived total gives/// `StorageLengthMismatch`. The owner allocates nothing further, so the link's memory use is fixed/// once the block is handed over. Teardown returns the block to the caller.pub const Memory = struct { phase: alloc_phase.capacity.Phase, capacity: MemoryCapacity, storage: Storage, directions: [2]Direction, pub const storage_alignment: usize = @alignOf(Entry); pub const Storage = []align(storage_alignment) u8; pub const Limits: type = MemoryLimits; pub const Capacity: type = MemoryCapacity; pub const Exhaustion = error{QueueFull}; pub const InitError = MemoryCapacity.DeriveError || error{StorageLengthMismatch}; pub const work_limits: alloc_phase.capacity.WorkLimits = .{ .transition_steps_max = 1, .cleanup_steps_per_call_max = 0, .cleanup_calls_at_capacity_max = 0, }; pub const claim: alloc_phase.capacity.Declaration = .{ .source = .{ .id = "quic.sim_memory", .kind = .phase_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "two_direction_queue_metadata", .lifetime = .transferred, .detail = "caller storage for two bounded datagram metadata queues", }, .{ .id = "two_direction_datagram_payload_bytes", .lifetime = .transferred, .detail = "caller storage for two bounded datagram payload regions", }, }, .excluded = &.{ "link policies, deterministic random state, statistics, and sequence counters", "per-direction drop patterns", "caller send slices and receive output slices", "socket, thread, clock, and operating-system state", }, }, .capacity = .{ .inputs = &.{ alloc_phase.capacity.bindInput( MemoryLimits, "queue_capacity", "queue_capacity", ), alloc_phase.capacity.bindInput( MemoryLimits, "datagram_capacity", "datagram_capacity", ), }, .type_selectors = &.{ alloc_phase.capacity.bindType(Entry, "entry"), }, .nodes = &.{ .{ .input = 0 }, .{ .constant = 2 }, .{ .product = .{ .left = 0, .right = 1 } }, .{ .scale = .{ .node = 2, .coefficient = .{ .size_of_concrete_type = 0 }, } }, .{ .input = 1 }, .{ .product = .{ .left = 2, .right = 4 } }, .{ .add = .{ .left = 3, .right = 5 } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 6, }}, }, .overload = .{ .kind = .reject_before_mutation, .detail = "full direction admission preserves queue metadata and payload bytes", }, .risks = .{ .transitive = .{ .status = .witnessed, .detail = "link operations use only fixed scans and caller-owned byte slices", }, .foreign = .{ .status = .excluded, .detail = "simulator memory crosses no operating-system or foreign boundary", }, }, .work = .{ .equation = "send and receive scan at most queue_capacity entries per direction", }, .obligations = &.{ .{ .key = "quic_sim_memory_capacity", .role = .capacity_model }, .{ .key = "quic_sim_memory_overload", .role = .overload }, .{ .key = "quic_sim_memory_transitive", .role = .transitive_risk }, .{ .key = "quic_sim_memory_work", .role = .work_bound }, }, }, .bindings = .{ .owner = @This(), .seal = .{ .family = alloc_phase.capacity.selector(@This().activate), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, .teardown = .{ .family = alloc_phase.capacity.selector(@This().deinit), .premise = .{ .class = .checked_semantic_fact, .authority = .checker, }, }, }, }; pub fn init(storage: Storage, limits: MemoryLimits) InitError!Memory { const capacity = try MemoryCapacity.derive(limits); if (storage.len != capacity.storage_bytes) return error.StorageLengthMismatch; const entries = std.mem.bytesAsSlice(Entry, storage[0..capacity.entry_bytes]); for (entries) |*entry| entry.* = .{}; const queue_count: usize = @intCast(capacity.queue_capacity); const payload = storage[capacity.entry_bytes..]; return .{ .phase = .initialization, .capacity = capacity, .storage = storage, .directions = .{ .{ .entries = entries[0..queue_count], .payload = payload[0..capacity.direction_payload_bytes], }, .{ .entries = entries[queue_count..], .payload = payload[capacity.direction_payload_bytes..], }, }, }; } pub fn activate(self: *Memory) void { std.debug.assert(self.phase == .initialization); std.debug.assert(self.storage.len == self.capacity.storage_bytes); std.debug.assert(self.directions[0].used == 0); std.debug.assert(self.directions[1].used == 0); self.phase = .steady; } fn freeCount(self: *const Memory, direction_index: usize) u32 { std.debug.assert(self.phase == .steady); std.debug.assert(direction_index < self.directions.len); const direction = &self.directions[direction_index]; std.debug.assert(direction.used <= self.capacity.queue_capacity); return self.capacity.queue_capacity - direction.used; } /// Reports whether one direction has room for a given number of further datagrams before the /// link queues anything, so a full direction is refused before any byte moves. Too little room /// gives `QueueFull`. The check reads the queue and changes nothing, so a refusal leaves the /// queued datagrams as they were. pub fn requireFree(self: *const Memory, from: End, copies: u2) Exhaustion!void { std.debug.assert(copies > 0); if (self.freeCount(outgoingIndex(from)) < copies) return error.QueueFull; } fn acquire(self: *Memory, direction_index: usize) Exhaustion!EntryRef { std.debug.assert(self.phase == .steady); std.debug.assert(direction_index < self.directions.len); var direction = &self.directions[direction_index]; if (direction.used == self.capacity.queue_capacity) return error.QueueFull; for (direction.entries, 0..) |*entry, index| { if (entry.occupied) continue; entry.occupied = true; direction.used += 1; return .{ .index = index, .entry = entry, .payload = payloadAt(direction, index, self.capacity.datagram_capacity), }; } unreachable; } fn release(self: *Memory, direction_index: usize, entry_index: usize) void { std.debug.assert(self.phase == .steady); std.debug.assert(direction_index < self.directions.len); var direction = &self.directions[direction_index]; std.debug.assert(entry_index < direction.entries.len); std.debug.assert(direction.entries[entry_index].occupied); std.debug.assert(direction.used > 0); direction.entries[entry_index].occupied = false; direction.used -= 1; } pub fn deinit(self: *Memory) Storage { std.debug.assert(self.phase == .steady); self.phase = .teardown; const storage = self.storage; self.* = undefined; return storage; }};Source: lib/quic/src/sim/policy.zig:15
zig
/// Impairments one direction of a simulated link applies, so a test builds one per direction and/// hands both to the link at startup. The link consults the policy on every send in that direction,/// and the two directions carry their own values. The default policy is a clean path: no loss, no/// duplication, no reordering, no delay, and the caller's full datagram capacity.pub const Policy = struct { /// The chance out of one thousand, or permille, that the link drops a datagram it has accepted, /// so a test makes the link throw datagrams away. A dropped datagram raises the sent count and /// the lost count together. loss_permille: u16 = 0, /// The chance out of one thousand that the link queues a second copy of a datagram, so a test /// makes the peer see a datagram twice. The two copies draw their delivery delays separately. duplicate_permille: u16 = 0, /// The chance out of one thousand that a newly queued copy trades delivery times with the most /// recently queued one, so a test makes datagrams arrive out of order. The swap covers the /// delivery time and the send order together, so the two copies exchange places in the queue. reorder_permille: u16 = 0, /// The smallest delivery delay in nanoseconds, which is itself a possible draw, so a test gives /// the path a floor on its latency. delay_min_ns: u64 = 0, /// The largest delivery delay in nanoseconds, which is itself a possible draw, so a test gives /// the path a ceiling on its latency. Each datagram takes a delay drawn uniformly between the /// two bounds. delay_max_ns: u64 = 0, /// The largest datagram in bytes the link will accept in this direction, or MTU, so a test /// makes the path refuse datagrams above a chosen size. Zero selects the caller's datagram /// capacity. A larger send is refused with `Oversize` and counted. mtu: u16 = 0, pub const ValidationError: type = PolicyValidationError; /// Checks one policy against the caller's datagram capacity for the link at startup so a /// nonsensical policy fails before any datagram moves. A loss, duplication, or reordering /// chance above one thousand gives `InvalidPermille`. A smallest delay above the largest gives /// `InvalidDelayBounds`. An MTU above the caller's datagram capacity gives /// `MtuExceedsCapacity`. pub fn validate(self: Policy, datagram_capacity: u16) PolicyValidationError!void { if (self.loss_permille > 1000) return error.InvalidPermille; if (self.duplicate_permille > 1000) return error.InvalidPermille; if (self.reorder_permille > 1000) return error.InvalidPermille; if (self.delay_min_ns > self.delay_max_ns) return error.InvalidDelayBounds; if (self.mtu > datagram_capacity) return error.MtuExceedsCapacity; std.debug.assert(self.loss_permille <= 1000); std.debug.assert(self.duplicate_permille <= 1000); std.debug.assert(self.reorder_permille <= 1000); std.debug.assert(self.delay_min_ns <= self.delay_max_ns); std.debug.assert(self.mtu <= datagram_capacity); } /// Returns the policy's own MTU, or the caller's datagram capacity when the policy leaves it at /// zero, so the link gets the size it must compare against on every send. The result is at most /// the caller's datagram capacity. pub fn effectiveMtu(self: Policy, datagram_capacity: u16) u16 { std.debug.assert(self.mtu <= datagram_capacity); const result = if (self.mtu == 0) datagram_capacity else self.mtu; std.debug.assert(result <= datagram_capacity); return result; }};Source: lib/quic/src/sim/policy.zig:3
zig
pub const ValidationError = error{ InvalidDelayBounds, InvalidPermille, MtuExceedsCapacity,};Source: lib/quic/src/sim/stats.zig:8
zig
/// The counters one direction of a simulated link keeps. A test reads one after a run to check how/// many datagrams the link dropped, duplicated, or reordered. It counts datagrams the caller sent,/// datagrams delivered, datagrams lost, extra copies made, copies whose delivery time was swapped,/// sends refused for a full queue, and sends refused for exceeding the MTU. Every counter starts at/// zero.pub const Stats = struct { sent: u64 = 0, delivered: u64 = 0, lost: u64 = 0, duplicated: u64 = 0, reordered: u64 = 0, queue_full: u64 = 0, oversize: u64 = 0,};Source: lib/quic/src/root.zig:49
zig
pub const sim = @import("sim/root.zig");Source: lib/quic/src/sim/root.zig
zig
const clock = @import("clock.zig");const link = @import("link.zig");const policy = @import("policy.zig");const stats = @import("stats.zig");pub const Clock = clock.Clock;pub const Capacity = link.Capacity;pub const CapacityError = link.CapacityError;pub const End = link.End;pub const Limits = link.Limits;pub const Link = link.Link;pub const Memory = link.Memory;pub const Policy = policy.Policy;pub const PolicyError = policy.ValidationError;pub const Stats = stats.Stats;Complete call list for sim.Link.send
10 direct calls.
lib.quic.src.sim.link.Link.delay[method] — private source atlib/quic/src/sim/link.zig:502in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.Link.enqueue[method] — private source atlib/quic/src/sim/link.zig:513in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.Link.event[method] — private source atlib/quic/src/sim/link.zig:485in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.Link.previousIndex[method] — private source atlib/quic/src/sim/link.zig:540in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.Link.swapDelivery[method] — private source atlib/quic/src/sim/link.zig:568in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.Link.takeDrop[method] — private source atlib/quic/src/sim/link.zig:492in nearest public ownerlib.quic.src.sim.linktiny.quic.sim.Memory.requireFree[method] atlib/quic/src/sim/link.zig:281lib.quic.src.sim.link.deliveryAt[function] — private source atlib/quic/src/sim/link.zig:588in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.increment[function] — private source atlib/quic/src/sim/link.zig:592in nearest public ownerlib.quic.src.sim.linklib.quic.src.sim.link.outgoingIndex[function] — private source atlib/quic/src/sim/link.zig:577in nearest public ownerlib.quic.src.sim.link
Audit
| Definitions | 37 |
|---|---|
| Public names | 37 |
| Members | 40 |
| Version | 26.7.0 |
| Revision | daab053ee433 |