The previous model was flawed. Consider the following case:
* The main loop looks as follows (pseudocode):
loop {
let _ = (tcp:1234).read_all()
wait(iface.poll())
}
* The remote end is continuously transmitting data and at some
point fills the window of (tcp:1234), stopping the transmission
afterwards.
* The local end processes the packets and, as a part of egress
routine, emits an ACK. That also updates the window, and
the socket's poll_at() routine returns None, since there is
nothing to transmit or retransmit.
* The local end now waits indefinitely even though it can start
processing the data in the socket buffers right now.
Run it without the `log` feature and in release mode:
$ cargo run --release \
--no-default-features \
--features std,phy-tap_interface,socket-tcp \
--example stress tap0 \
[reader|writer]
There are currently two bugs exposed by it:
* a crash in the reader mode,
* slow-down in the writer mode.
Before this commit, anything that touched RawSocket or TapInterface
worked partly by accident and partly because of a horrible crutch
that resulted in massive latencies as well as inevitable packet loss
every time an ARP request had to be issued. Also, there was no way
to use poll() other than by continuously calling it in a busy loop.
After this commit, poll() indicates when the earliest timer expires,
and so the caller can sleep until that moment (or until packets
arrive).
Note that there is a subtle problem remaining: every time poll()
is called, every socket with a pending outbound packet whose
IP address doesn't correspond to a MAC address will send a new
ARP request, resulting in potentially a whole lot of such requests.
ARP rate limiting is a separate topic though.
Various parts of smoltcp require an arrow of time; a monotonically
increasing timestamp. Most obviously this is TCP sockets, but
the tracer and the pcap writer devices also benefit from having
timestamps. There are a few ways this could be implemented:
1. using a static Cell, global for the entire smoltcp crate;
2. using a static method on Device;
3. using an instance method on Device;
4. passing the current timestamp into *Interface::poll.
The first two options are undesirable because they create a notion
of global clock, and interfere e.g. with mocking.
The third option is undesirable because not all devices are
inherently tied to a particular clock, e.g. a loopback device isn't.
Therefore, the timestamp is injected into both sockets and devices
through the *Interface::poll method.