///| Embedding the protocol model in your own program.
///
/// Nothing here reads a scenario file or parses an argument: the library is
/// called directly, which is what a downstream project does when it wants a
/// deterministic answer to a network question inside its own tooling or test
/// suite. Ten lines to a measurement, and the same seed always gives the same
/// answer.
///|
/// One direction of the path, in the units the library speaks.
pub fn link(name : String, loss : Double) -> @net.LinkSpec {
@net.LinkSpec::new(
name,
10_000_000L,
@sim.Time::from_ms(25L),
@net.QueueSpec::packets(64),
).with_loss(loss)
}
///|
/// What one embedded run measured.
pub(all) struct Transfer {
algorithm : String
delivered_bytes : Int
elapsed : @sim.Time
retransmits : Int
timeouts : Int
srtt : @sim.Time
} derive(Eq, Debug)
///|
/// Transfer `bytes` with the named algorithm over a path that drops `loss` of
/// the packets, and report what it cost.
pub fn transfer(
algorithm : &@tcp.CongestionControl,
bytes : Int,
loss : Double,
seed : UInt64,
) -> Transfer {
let sim = @sim.Sim::new(seed)
let mss = 1000
let pair = @tcp.LinkPair::new_with_cc(
sim,
@tcp.TcpConfig::new(mss, 64000, 1000U),
@tcp.TcpConfig::new(mss, 64000, 5000U),
link("up", loss),
link("down", loss),
seed,
algorithm,
)
// The clock is read around the transfer rather than at the end of the run:
// the connection keeps leaving superseded timer events behind, and draining
// them would measure the last of those instead of the transfer.
let start = sim.now()
pair.connect(sim)
sim.step_until(() => pair.client.state() == @tcp.Established) |> ignore
pair.client.send(sim, Bytes::make(bytes, b'x'))
sim.step_until(() => pair.server.delivered_bytes() == bytes) |> ignore
{
algorithm: pair.client.cc_name(),
delivered_bytes: pair.server.delivered_bytes(),
elapsed: sim.now() - start,
retransmits: pair.client.retransmits(),
timeouts: pair.client.timeouts(),
srtt: match pair.client.srtt() {
Some(value) => value
None => @sim.Time::zero()
},
}
}
///|
/// The algorithms the library ships, by name, so a caller that has a string
/// from a config file does not have to know the constructors.
pub fn algorithm(name : String, mss : Int) -> &@tcp.CongestionControl {
match name {
"cubic" => @tcp.cubic(mss)
"none" => @tcp.without_congestion_control()
_ => @tcp.reno(mss)
}
}
///|
/// A report line, so the entry point stays a few lines long.
pub fn Transfer::to_line(self : Transfer) -> String {
self.algorithm +
" " +
self.delivered_bytes.to_string() +
" bytes in " +
self.elapsed.render() +
", " +
self.retransmits.to_string() +
" retransmits, " +
self.timeouts.to_string() +
" timeouts, srtt " +
self.srtt.render()
}