///|
/// The real-socket `Transport` the client runs on.
///
/// The client state machines never touch a socket; this file is the one
/// implementation of `Transport` that does, so the same `get` / `put` runs
/// against a real UDP port here and against the in-memory pair in tests.
///
/// The socket is deliberately *not* connected to the server's port. TFTP's
/// TID rule (RFC 1350, section 4) says the server answers a request from a
/// fresh port, so a socket locked onto port 69 would drop every reply and
/// have the kernel answer the server with ICMP. The first datagram the peer
/// sends therefore *defines* the TID this transfer talks to, and only that
/// address is accepted from then on.
///|
/// A client `Transport` bound to a local ephemeral port.
pub struct SocketTransport {
socket : @socket.UdpServer
/// The request address the caller asked for. The reply may come from a
/// different port, which becomes `peer` once it is seen.
request_peer : String
/// The address the peer is actually using, learnt from its first packet.
mut peer : String
address : String
}
///|
/// Binds an ephemeral local port for the transfer's own TID.
///
/// Port 0 lets the OS pick: the server answers the request back to whatever
/// port it came from, and that port is this transfer's TID.
pub async fn SocketTransport::new(remote : String) -> SocketTransport {
let socket = @socket.UdpServer(@socket.Addr::parse("0.0.0.0:0"))
{
socket,
request_peer: remote,
peer: remote,
address: socket.addr.to_string(),
}
}
///|
/// Releases the local port.
pub fn SocketTransport::close(self : SocketTransport) -> Unit {
self.socket.close()
}
///|
pub extend SocketTransport with Transport::{recv, local_peer, send}
///|
pub impl Transport for SocketTransport with fn local_peer(self) {
self.address
}
///|
/// Sends to the peer address. The request goes to the well-known port; every
/// later packet goes to the TID the peer's first reply came from.
pub impl Transport for SocketTransport with fn send(self, payload, peer) {
// Every packet goes to the TID the peer's reply came from once it is known.
// Only the request itself — sent before any reply — goes to the well-known
// port the caller named, because that is the one port the server listens on.
let target = if self.peer == self.request_peer { peer } else { self.peer }
self.socket.sendto(payload, @socket.Addr::parse(target))
}
///|
pub impl Transport for SocketTransport with fn recv(self, peer, timeout_ms) {
let buffer = FixedArray::make(65536, b'\x00')
let expected = if peer == self.request_peer { self.peer } else { peer }
// One deadline covers the whole call: `read_expected` keeps reading past
// datagrams from anyone else, so a stray packet is dropped without spending
// the caller's budget.
let received = @async.with_timeout_opt(timeout_ms, () => {
self.read_expected(buffer, expected)
})
match received {
Some((payload, from)) => {
// The first reply may arrive from a port other than the one the request
// went to; that address is the peer's TID from here on.
if expected == self.request_peer {
self.peer = from
}
Recv::Packet(Datagram::new(payload, from))
}
None => Recv::Timeout
}
}
///|
/// Reads datagrams until one came from `expected`, and returns it.
///
/// A packet from anyone else is discarded and the read continues: that is what
/// `Transport::recv(peer, ...)` promises — only the peer's packets are ever
/// seen — and it is also how a stale packet from a previous TID is ignored.
async fn SocketTransport::read_expected(
self : SocketTransport,
buffer : FixedArray[Byte],
expected : String,
) -> (Bytes, String) {
while true {
let (length, from) = self.socket.recvfrom(buffer)
let address = from.to_string()
if address == expected || expected == self.request_peer {
break (buffer.unsafe_reinterpret_as_bytes()[:length].to_owned(), address)
}
} nobreak {
(
// Unreachable: the loop only ends via `break`.
Bytes::new(0),
expected,
)
}
}