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)