Landin library reference discuss source

core/io/loopback.ldn

1--  A network in memory, beside the memory world: a provider of `network`
2--  whose sockets are the caller's records, for a test that runs a client
3--  and a server in one thread and wants the same answer every run.
4
5import core/mem
6
7--- Caller-owned state of a memory network: the next port it gives a
8--- socket bound to port 0, its counters, and what a test may set.
9--- Declare it `zeroed` and give it to `network_over`, which fills it; it
10--- must outlive the network's handle.  `without_ipv6` makes IPv6 missing,
11--- as on a system that does not have it, so every IPv6 address is
12--- `address_unavailable`; `fail_at` makes the network's call of that
13--- number, counted from one in `calls`, fail `io_failed`, and zero never.
14--- `connections` counts connections made, `datagrams` datagrams
15--- delivered and `lost` datagrams no socket took.
16public network_space: type = struct
17    next_port: usize
18    arrivals: usize
19    calls: usize
20    fail_at: usize
21    without_ipv6: bool
22    connections: usize
23    datagrams: usize
24    lost: usize
25end network_space
26
27--  What a socket record is, `role`'s values.
28free_slot: usize = 0
29listening_slot: usize = 1
30connection_slot: usize = 2
31datagram_slot: usize = 3
32
33--- One socket of a memory network, the caller's: `buffer` holds what has
34--- arrived for it and not yet been received, its receive buffer, so a
35--- send that does not fit waits as a real one would.  Make each with
36--- `socket_over`; the other fields are the network's.  A free record is
37--- a socket the network can open; one that is open is a file of the
38--- network until `close_socket` frees it again.
39public network_socket: type = struct
40    buffer: []mut u8
41    role: usize
42    local: address
43    peer: address
44    partner: usize
45    listener: usize
46    order: usize
47    pending: bool
48    start: usize
49    filled: usize
50    receiving_closed: bool
51    sending_closed: bool
52    peer_finished: bool
53    peer_gone: bool
54    receive_limit: u64
55    send_limit: u64
56    no_delay: bool
57end network_socket
58
59--- A free socket record whose received bytes wait in `buffer`.
60public socket_over: (buffer: []mut u8)
61                    -> (slot: network_socket from buffer) =
62    slot = (buffer: buffer, role: free_slot,
63            local: unspecified(ipv4, 0), peer: unspecified(ipv4, 0),
64            partner: 0, listener: 0, order: 0, pending: false, start: 0,
65            filled: 0, receiving_closed: false, sending_closed: false,
66            peer_finished: false, peer_gone: false, receive_limit: 0,
67            send_limit: 0, no_delay: false)
68end socket_over
69
70network_value: type = struct
71    state: ptr mut network_space
72    sockets: []mut network_socket
73end network_value
74
75--- A network of one machine in memory, a provider of `network`, `reader`
76--- and `writer` over the caller's socket records; copies share them.  Its
77--- only interfaces are the loopback addresses, `127.0.0.0/8` and `::1`:
78--- binding another address is `address_unavailable`, connecting to one
79--- `unreachable`, and connecting to a port nothing listens on `refused`.
80--- A port is in use for a binding of its family that the address of
81--- another socket of its protocol at that port would also take, the
82--- unspecified address taking every one, as Linux has it; Darwin and
83--- FreeBSD let a listener take a specific address beside one on the
84--- unspecified address.
85--- A connection and a datagram arrive at once, whole and in order, and
86--- a datagram sent to a port no socket holds, or larger than the room its
87--- socket has left, is lost, as UDP's may be.
88---
89--- Nothing in it waits, since in one thread nothing else could ever end
90--- the wait: where a real socket would wait, for a connection to accept,
91--- for bytes to receive or for room to send into, it fails `timed_out` at
92--- once, whether or not a limit was set, after a send has delivered what
93--- fits.  A send to a connection whose peer has closed is `disconnected`
94--- at once, where a real one may take one send before it says so.  It
95--- does not model loss on a connection, a peer's reset, partial reads, a
96--- port held after its socket closed, nor a zone.
97public memory_network: type = network_value
98
99--- Fill `state` for a memory network over `sockets`, every one of them
100--- free, and return its handle.  Ports bound to port 0 are given from
101--- 49152 up.
102public network_over: (inout state: network_space,
103                      sockets: []mut network_socket)
104                     -> (host: memory_network from state, sockets) =
105    state = (next_port: 49152, arrivals: 0, calls: 0, fail_at: 0,
106             without_ipv6: false, connections: 0, datagrams: 0, lost: 0)
107    for index in usize(0)..<network_slot_count(sockets) do
108        sockets[index].role = free_slot
109        sockets[index].start = 0
110        sockets[index].filled = 0
111    end for
112    host = (state: addr state, sockets: sockets)
113end network_over
114
115network_slot_count: (values: []network_socket) -> (count: usize) =
116    count = lenof values
117end network_slot_count
118
119--  Count a call, failing the one the test chose.
120counted: (self: memory_network) -> none ! io_failed =
121    state: ptr mut network_space = self.state
122    inc state.val.calls
123    fail io_failed when state.val.calls == state.val.fail_at
124end counted
125
126--  Whether the network has the address's family.
127has_family: (self: memory_network, at: address) -> (yes: bool) =
128    yes = at.family == ipv4 or not self.state.val.without_ipv6
129end has_family
130
131--  Whether an address is one of this machine's: a loopback address.
132is_local: (at: address) -> (yes: bool) =
133    if at.family == ipv4 then
134        yes = at.octets[0] == 127
135    else
136        yes = true
137        for index in usize(0)..<15 do
138            if at.octets[index] <> 0 then yes = false end if
139        end for
140        yes = yes and at.octets[15] == 1
141    end if
142end is_local
143
144--  Whether a socket bound to `held` takes what is sent to `wanted`: the
145--  same family and port, and the same address or the unspecified one.
146takes: (held: address, wanted: address) -> (yes: bool) =
147    yes = held.family == wanted.family and held.port == wanted.port
148          and (is_unspecified(held) or same_address(held, wanted))
149end takes
150
151--  Whether two bindings would take the same traffic.
152overlaps: (one: address, other: address) -> (yes: bool) =
153    yes = one.family == other.family and one.port == other.port
154          and (is_unspecified(one) or is_unspecified(other)
155               or same_address(one, with_port(other, one.port)))
156end overlaps
157
158--  Whether a socket of `role` holds a port that overlaps `at`: listeners
159--  and connections hold TCP's ports, datagram sockets UDP's.
160port_taken: (self: memory_network, at: address, role: usize)
161            -> (taken: bool) =
162    taken = false
163    for index in usize(0)..<network_slot_count(self.sockets) do
164        held: ptr network_socket = addr self.sockets[index]
165        same_kind: bool = if role == datagram_slot
166        then held.val.role == datagram_slot
167        else held.val.role == listening_slot
168             or held.val.role == connection_slot end if
169        if same_kind and overlaps(held.val.local, at) then taken = true end if
170    end for
171end port_taken
172
173--  The address with a free port of `role`'s kind in place of port 0.
174with_free_port: (self: memory_network, at: address, role: usize)
175                -> (given: address) ! address_in_use =
176    given = at
177    return when at.port <> 0
178    state: ptr mut network_space = self.state
179    for tries in usize(0)..<16384 do
180        _ = tries
181        port: usize = state.val.next_port
182        state.val.next_port = if port >= 65535 then 49152 else port + 1 end if
183        given = with_port(at, u16(port))
184        return when not port_taken(self, given, role)
185    end for
186    fail address_in_use
187end with_free_port
188
189--  A free record's index.
190free_index: (self: memory_network) -> (index: usize) ! io_failed =
191    for at in usize(0)..<network_slot_count(self.sockets) do
192        if self.sockets[at].role == free_slot then
193            index = at
194            return
195        end if
196    end for
197    fail io_failed
198end free_index
199
200--  Open a record as a socket of `role` at `local`.
201opened: (self: memory_network, index: usize, role: usize, local: address)
202        -> none =
203    slot: ptr mut network_socket = addr self.sockets[index]
204    slot.val.role = role
205    slot.val.local = local
206    slot.val.peer = unspecified(local.family, 0)
207    slot.val.partner = index
208    slot.val.listener = index
209    slot.val.order = 0
210    slot.val.pending = false
211    slot.val.start = 0
212    slot.val.filled = 0
213    slot.val.receiving_closed = false
214    slot.val.sending_closed = false
215    slot.val.peer_finished = false
216    slot.val.peer_gone = false
217    slot.val.receive_limit = 0
218    slot.val.send_limit = 0
219    slot.val.no_delay = false
220end opened
221
222--  The open socket a file is, of `role` when that is not free.
223slot_of: (self: memory_network, socket: file, role: usize)
224         -> (slot: ptr mut network_socket from self) ! io_failed =
225    index: usize = usize(socket)
226    fail io_failed when index >= network_slot_count(self.sockets)
227    slot = addr self.sockets[index]
228    fail io_failed when slot.val.role == free_slot
229      or (role <> free_slot and slot.val.role <> role)
230      or slot.val.pending
231end slot_of
232
233--  Bind a socket of `role` at `at`.
234bound_at: (self: memory_network, at: address, role: usize)
235          -> (socket: file)
236          ! address_in_use | address_unavailable | io_failed =
237    try counted(self)
238    fail address_unavailable when not has_family(self, at)
239      or not (is_unspecified(at) or is_local(at))
240    fail address_in_use when at.port <> 0 and port_taken(self, at, role)
241    index: usize = try free_index(self)
242    local: address = try with_free_port(self, at, role)
243    opened(self, index, role, local)
244    socket = file(index)
245end bound_at
246
247network_listen: (self: memory_network, at: address) -> (listener: file)
248                ! address_in_use | address_unavailable | no_access
249                | io_failed =
250    listener = try bound_at(self, at, listening_slot)
251end network_listen
252
253network_open_datagram: (self: memory_network, at: address)
254                       -> (socket: file)
255                       ! address_in_use | address_unavailable | no_access
256                       | io_failed =
257    socket = try bound_at(self, at, datagram_slot)
258end network_open_datagram
259
260network_connect: (self: memory_network, to: address) -> (connected: file)
261                 ! refused | unreachable | address_unavailable | timed_out
262                 | io_failed =
263    try counted(self)
264    fail address_unavailable when not has_family(self, to)
265    fail unreachable when not is_local(to)
266    mut listener: usize = network_slot_count(self.sockets)
267    for index in usize(0)..<network_slot_count(self.sockets) do
268        if self.sockets[index].role == listening_slot
269          and takes(self.sockets[index].local, to) then
270            listener = index
271        end if
272    end for
273    fail refused when listener == network_slot_count(self.sockets)
274    client: usize = try free_index(self)
275    self.sockets[client].role = connection_slot
276    server: usize = free_index(self) else (problem)
277        self.sockets[client].role = free_slot
278        fail problem
279    end
280    self.sockets[client].role = free_slot
281    local: address = with_free_port(self, loopback(to.family, 0),
282                                    connection_slot) else (problem)
283        _ = problem
284        fail io_failed
285    end
286    opened(self, client, connection_slot, local)
287    opened(self, server, connection_slot, to)
288    state: ptr mut network_space = self.state
289    self.sockets[client].peer = to
290    self.sockets[client].partner = server
291    self.sockets[server].peer = local
292    self.sockets[server].partner = client
293    self.sockets[server].pending = true
294    self.sockets[server].listener = listener
295    self.sockets[server].order = state.val.arrivals
296    inc state.val.arrivals
297    inc state.val.connections
298    connected = file(client)
299end network_connect
300
301network_accept: (self: memory_network, listener: file) -> (accepted: file)
302                ! timed_out | io_failed =
303    try counted(self)
304    _ = try slot_of(self, listener, listening_slot)
305    mut found: usize = network_slot_count(self.sockets)
306    for index in usize(0)..<network_slot_count(self.sockets) do
307        candidate: ptr network_socket = addr self.sockets[index]
308        if candidate.val.pending
309          and candidate.val.listener == usize(listener)
310          and (found == network_slot_count(self.sockets)
311               or candidate.val.order < self.sockets[found].order) then
312            found = index
313        end if
314    end for
315    fail timed_out when found == network_slot_count(self.sockets)
316    self.sockets[found].pending = false
317    accepted = file(found)
318end network_accept
319
320--  Move what waits in a socket's buffer to its front.
321compacted: (slot: ptr mut network_socket) -> none =
322    return when slot.val.start == 0
323    waiting: usize = slot.val.filled - slot.val.start
324    mem.copy(slot.val.buffer[0..<waiting],
325             slot.val.buffer[slot.val.start..<slot.val.filled]) else (problem)
326        _ = problem
327    end
328    slot.val.start = 0
329    slot.val.filled = waiting
330end compacted
331
332--  Deliver as much of `bytes` as the partner's buffer has room for: how
333--  much.
334delivered: (self: memory_network, sender: ptr mut network_socket,
335            bytes: []u8) -> (count: usize) =
336    to: ptr mut network_socket = addr self.sockets[sender.val.partner]
337    count = lenof bytes
338    return when to.val.receiving_closed
339    compacted(to)
340    room: []mut u8 = to.val.buffer
341    left: usize = lenof room - to.val.filled
342    if count > left then count = left end if
343    ends: usize = to.val.filled + count
344    mem.copy(to.val.buffer[to.val.filled..<ends], bytes[0..<count])
345        else (problem)
346        _ = problem
347    end
348    to.val.filled = ends
349end delivered
350
351--  Send what fits of `bytes` on a connection: how much.
352sent_part: (self: memory_network, connection: file, bytes: []u8)
353           -> (count: usize) ! disconnected | io_failed =
354    try counted(self)
355    slot: ptr mut network_socket = try slot_of(self, connection,
356                                               connection_slot)
357    fail disconnected when slot.val.sending_closed or slot.val.peer_gone
358    count = delivered(self, slot, bytes)
359end sent_part
360
361network_send: (self: memory_network, connection: file, bytes: []u8)
362              -> none ! disconnected | timed_out | io_failed =
363    count: usize = try sent_part(self, connection, bytes)
364    fail timed_out when count < lenof bytes
365end network_send
366
367network_receive: (self: memory_network, connection: file, into: []mut u8)
368                 -> (count: usize)
369                 ! disconnected | timed_out | io_failed =
370    try counted(self)
371    slot: ptr mut network_socket = try slot_of(self, connection,
372                                               connection_slot)
373    count = 0
374    return when slot.val.receiving_closed or lenof into == 0
375    waiting: usize = slot.val.filled - slot.val.start
376    if waiting == 0 then
377        return when slot.val.peer_finished
378        fail timed_out
379    end if
380    count = if waiting < lenof into then waiting else lenof into end if
381    ends: usize = slot.val.start + count
382    mem.copy(into[0..<count], slot.val.buffer[slot.val.start..<ends])
383        else (problem)
384        _ = problem
385        fail io_failed
386    end
387    slot.val.start = ends
388end network_receive
389
390--  A datagram's record in its receiver's buffer: the sender's family,
391--  sixteen bytes of address and two of port, two of length, and then the
392--  bytes.
393datagram_header: usize = 21
394
395--- The longest datagram a memory network carries, as IPv4's UDP does.
396public longest_datagram: usize = 65507
397
398--  Put a datagram from `sender` in `receiver`'s buffer, or count it lost
399--  when it has no room left.
400queued: (self: memory_network, receiver: ptr mut network_socket,
401         sender: address, bytes: []u8) -> none ! io_failed =
402    state: ptr mut network_space = self.state
403    compacted(receiver)
404    room: []mut u8 = receiver.val.buffer
405    needed: usize = datagram_header + lenof bytes
406    if needed > lenof room - receiver.val.filled then
407        inc state.val.lost
408        return
409    end if
410    at: usize = receiver.val.filled
411    receiver.val.buffer[at] = if sender.family == ipv4 then 4 else 6 end if
412    for octet in usize(0)..<16 do
413        receiver.val.buffer[at + 1 + octet] = sender.octets[octet]
414    end for
415    receiver.val.buffer[at + 17] = u8(sender.port >> 8)
416    receiver.val.buffer[at + 18] = u8(sender.port & 255)
417    receiver.val.buffer[at + 19] = u8(lenof bytes >> 8)
418    receiver.val.buffer[at + 20] = u8(lenof bytes & 255)
419    mem.copy(receiver.val.buffer[at + datagram_header..<at + needed],
420             bytes) else (problem)
421        _ = problem
422        fail io_failed
423    end
424    receiver.val.filled = at + needed
425    inc state.val.datagrams
426end queued
427
428network_send_to: (self: memory_network, socket: file, bytes: []u8,
429                  to: address) -> none
430                 ! unreachable | timed_out | io_failed =
431    try counted(self)
432    slot: ptr mut network_socket = try slot_of(self, socket, datagram_slot)
433    fail io_failed when lenof bytes > longest_datagram
434      or to.family <> slot.val.local.family
435    fail unreachable when not is_local(to)
436    for index in usize(0)..<network_slot_count(self.sockets) do
437        receiver: ptr mut network_socket = addr self.sockets[index]
438        if receiver.val.role == datagram_slot
439          and takes(receiver.val.local, to) then
440            try queued(self, receiver, slot.val.local, bytes)
441            return
442        end if
443    end for
444    state: ptr mut network_space = self.state
445    inc state.val.lost
446end network_send_to
447
448network_receive_from: (self: memory_network, socket: file,
449                       into: []mut u8)
450                      -> (received: datagram) ! timed_out | io_failed =
451    try counted(self)
452    slot: ptr mut network_socket = try slot_of(self, socket, datagram_slot)
453    fail timed_out when slot.val.filled - slot.val.start < datagram_header
454    at: usize = slot.val.start
455    family: address_family = if slot.val.buffer[at] == 4 then ipv4
456                             else ipv6 end if
457    mut sender: address = unspecified(family, 0)
458    for octet in usize(0)..<16 do
459        sender.octets[octet] = slot.val.buffer[at + 1 + octet]
460    end for
461    sender.port = (u16(slot.val.buffer[at + 17]) << 8)
462                   | u16(slot.val.buffer[at + 18])
463    length: usize = (usize(slot.val.buffer[at + 19]) << 8)
464                     | usize(slot.val.buffer[at + 20])
465    count: usize = if length < lenof into then length else lenof into end if
466    begins: usize = at + datagram_header
467    mem.copy(into[0..<count], slot.val.buffer[begins..<begins + count])
468        else (problem)
469        _ = problem
470        fail io_failed
471    end
472    slot.val.start = begins + length
473    received = (count: count, sender: sender)
474end network_receive_from
475
476network_shutdown: (self: memory_network, connection: file,
477                   stopping: direction) -> none ! io_failed =
478    try counted(self)
479    slot: ptr mut network_socket = try slot_of(self, connection,
480                                               connection_slot)
481    if stopping <> sending then
482        slot.val.receiving_closed = true
483        slot.val.start = 0
484        slot.val.filled = 0
485    end if
486    if stopping <> receiving then
487        slot.val.sending_closed = true
488        self.sockets[slot.val.partner].peer_finished = true
489    end if
490end network_shutdown
491
492network_local_address: (self: memory_network, socket: file)
493                       -> (at: address) ! io_failed =
494    try counted(self)
495    slot: ptr mut network_socket = try slot_of(self, socket, free_slot)
496    at = slot.val.local
497end network_local_address
498
499network_peer_address: (self: memory_network, connection: file)
500                      -> (at: address) ! io_failed =
501    try counted(self)
502    slot: ptr mut network_socket = try slot_of(self, connection,
503                                               connection_slot)
504    at = slot.val.peer
505end network_peer_address
506
507network_set_timeouts: (self: memory_network, socket: file,
508                       receive_limit: duration, send_limit: duration)
509                      -> none ! io_failed =
510    try counted(self)
511    slot: ptr mut network_socket = try slot_of(self, socket, free_slot)
512    slot.val.receive_limit = u64(receive_limit)
513    slot.val.send_limit = u64(send_limit)
514end network_set_timeouts
515
516network_set_no_delay: (self: memory_network, connection: file, on: bool)
517                      -> none ! io_failed =
518    try counted(self)
519    slot: ptr mut network_socket = try slot_of(self, connection,
520                                               connection_slot)
521    slot.val.no_delay = on
522end network_set_no_delay
523
524--  The other end of a connection that has gone: what it already received
525--  stays, and then its stream ends, and what it sends is disconnected.
526orphaned: (self: memory_network, index: usize) -> none =
527    other: ptr mut network_socket = addr self.sockets[index]
528    other.val.peer_finished = true
529    other.val.peer_gone = true
530end orphaned
531
532network_close_socket: (self: memory_network, sink socket: file)
533                      -> none ! io_failed =
534    try counted(self)
535    slot: ptr mut network_socket = try slot_of(self, socket, free_slot)
536    index: usize = usize(socket)
537    if slot.val.role == connection_slot then
538        orphaned(self, slot.val.partner)
539    elsif slot.val.role == listening_slot then
540        --  Connections waiting to be accepted are reset with it.
541        for pending in usize(0)..<network_slot_count(self.sockets) do
542            waiting: ptr mut network_socket = addr self.sockets[pending]
543            if waiting.val.pending and waiting.val.listener == index then
544                orphaned(self, waiting.val.partner)
545                waiting.val.pending = false
546                waiting.val.role = free_slot
547            end if
548        end for
549    end if
550    slot.val.role = free_slot
551    slot.val.start = 0
552    slot.val.filled = 0
553end network_close_socket
554
555network_read: (self: memory_network, opened: file, into: []mut u8)
556              -> (count: usize) ! io_failed =
557    count = network_receive(self, opened, into) else (problem)
558        _ = problem
559        fail io_failed
560    end
561end network_read
562
563network_write: (self: memory_network, opened: file, bytes: []u8)
564               -> none ! io_failed =
565    network_send(self, opened, bytes) else (problem)
566        _ = problem
567        fail io_failed
568    end
569end network_write
570
571--  What fits, or `io_failed` when nothing does, since a write that makes
572--  no progress breaks the writer's contract.
573network_write_some: (self: memory_network, opened: file, bytes: []u8)
574                    -> (count: usize) ! io_failed =
575    count = sent_part(self, opened, bytes) else (problem)
576        _ = problem
577        fail io_failed
578    end
579    fail io_failed when count == 0 and lenof bytes > 0
580end network_write_some
581
582memory_network is reader (read: network_read)
583memory_network is writer (write: network_write,
584                          write_some: network_write_some)
585memory_network is network
586    (connect: network_connect, listen: network_listen,
587     accept: network_accept, open_datagram: network_open_datagram,
588     send: network_send, receive: network_receive,
589     send_to: network_send_to, receive_from: network_receive_from,
590     shutdown: network_shutdown, local_address: network_local_address,
591     peer_address: network_peer_address,
592     set_timeouts: network_set_timeouts,
593     set_no_delay: network_set_no_delay,
594     close_socket: network_close_socket)