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