~ehmry/nim_taps

6ec1491c178bd37491ca55c012675bd525391e29 — Emery Hemingway 5 months ago a27d4f2 20240402
Rewrite Solo5 implementation for CPS
M .gitignore => .gitignore +1 -0
@@ 1,3 1,4 @@
/.direnv
/.tup
/nim.cfg
*.spt

M Tuprules.tup => Tuprules.tup +2 -0
@@ 1,3 1,5 @@
NIM = $(DIRENV) $(NIM)
NIM_GROUPS += $(TUP_CWD)/<lock>
NIM_FLAGS += --path:$(TUP_CWD)/../cps
NIM_FLAGS += --path:$(TUP_CWD)/../getdns/src
NIM_FLAGS += --path:$(TUP_CWD)/../solo5_dispatcher/pkg

A examples/minimal_example/Tupfile => examples/minimal_example/Tupfile +2 -0
@@ 0,0 1,2 @@
include_rules
: foreach *.nim |> !nim |>

A lock.json => lock.json +87 -0
@@ 0,0 1,87 @@
{
  "depends": [
    {
      "method": "fetchzip",
      "packages": [
        "cps"
      ],
      "path": "/nix/store/8gbhwni0akqskdb3qhn5nfgv6gkdz0vz-source",
      "rev": "c90530ac57f98a842b7be969115c6ef08bdcc564",
      "sha256": "0h8ghs2fqg68j3jdcg7grnxssmllmgg99kym2w0a3vlwca1zvr62",
      "srcDir": "",
      "url": "https://github.com/ehmry/cps/archive/c90530ac57f98a842b7be969115c6ef08bdcc564.tar.gz"
    },
    {
      "method": "fetchzip",
      "packages": [
        "getdns"
      ],
      "path": "/nix/store/x9xmn7w4k6jg8nv5bnx148ibhnsfh362-source",
      "rev": "c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6",
      "sha256": "1sbgx2x51szr22i72n7c8jglnfmr8m7y7ga0v85d58fwadiv7g6b",
      "srcDir": "src",
      "url": "https://git.sr.ht/~ehmry/getdns-nim/archive/c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6.tar.gz"
    },
    {
      "method": "fetchzip",
      "packages": [
        "getdns"
      ],
      "path": "/nix/store/x9xmn7w4k6jg8nv5bnx148ibhnsfh362-source",
      "rev": "c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6",
      "sha256": "1sbgx2x51szr22i72n7c8jglnfmr8m7y7ga0v85d58fwadiv7g6b",
      "srcDir": "src",
      "url": "https://git.sr.ht/~ehmry/getdns-nim/archive/c73cbe288d9f9480586b8fa87f6d794ffb6a6ce6.tar.gz"
    },
    {
      "method": "fetchzip",
      "packages": [
        "sys"
      ],
      "path": "/nix/store/vf9ls2wip6d8xhsi3rjh0dqsqg597i6b-source",
      "rev": "c117ee60542f084525f254e6ade590675a6a2ed6",
      "sha256": "12qzx2lnh84xqfgypy0pka8nflq0y8n1izfwx8mb4zya5nzawmyf",
      "srcDir": "src",
      "url": "https://github.com/alaviss/nim-sys/archive/c117ee60542f084525f254e6ade590675a6a2ed6.tar.gz"
    },
    {
      "date": "2024-04-02T15:38:57+01:00",
      "deepClone": false,
      "fetchLFS": false,
      "fetchSubmodules": true,
      "hash": "sha256-iZb9aAgYr4FGkqfIg49QWiCqeizIi047kFhugHiP8o0=",
      "leaveDotGit": false,
      "method": "git",
      "packages": [
        "solo5_dispatcher"
      ],
      "path": "/nix/store/sf5dgj2ljvahcm6my7d61ibda51vnrii-solo5_dispatcher",
      "rev": "a7a894a96a2221284012800e6fd32923d83d20bd",
      "sha256": "13gjixw80vjqj0xlx2y85ixal82sa27q7j57j9383bqq11lgv5l9",
      "srcDir": "pkg",
      "url": "https://git.sr.ht/~ehmry/solo5_dispatcher"
    },
    {
      "method": "fetchzip",
      "packages": [
        "cps"
      ],
      "path": "/nix/store/phdf6siqbhj7vx4qq507lzla81si60iz-source",
      "rev": "58772ff9ddb38a4b2ec52da142d8532ba2fe7039",
      "sha256": "1lph7v27nqwgm3a0ssi8q348gjrkjwgqc50agw38j7xif6wj80cw",
      "srcDir": "",
      "url": "https://github.com/ehmry/cps/archive/58772ff9ddb38a4b2ec52da142d8532ba2fe7039.tar.gz"
    },
    {
      "method": "fetchzip",
      "packages": [
        "stew"
      ],
      "path": "/nix/store/mqg8qzsbcc8xqabq2yzvlhvcyqypk72c-source",
      "rev": "3c91b8694e15137a81ec7db37c6c58194ec94a6a",
      "sha256": "17lfhfxp5nxvld78xa83p258y80ks5jb4n53152cdr57xk86y07w",
      "srcDir": "",
      "url": "https://github.com/status-im/nim-stew/archive/3c91b8694e15137a81ec7db37c6c58194ec94a6a.tar.gz"
    }
  ]
}

M shell.nix => shell.nix +2 -2
@@ 1,6 1,6 @@
let pkgs = import <nixpkgs> { };
in pkgs.buildNimPackage {
  name = "dummy";
  buildInputs = [ pkgs.getdns ];
  nativeBuildInputs = [ pkgs.pkg-config ];
  buildInputs = builtins.attrValues { inherit (pkgs) getdns solo5; };
  nativeBuildInputs = builtins.attrValues { inherit (pkgs) pkg-config solo5; };
}

M src/taps/lwip.nim => src/taps/lwip.nim +4 -1
@@ 3,12 3,15 @@

## Meta-module for building LwIP

when defined(nimPreviewSlimSystem):
  import std/assertions

const
  ipv4Enabled* {.booldefine.}: bool = false
  ipv6Enabled* {.booldefine.}: bool = false

when not (ipv4Enabled or ipv6Enabled):
  {.error: "neither ipv4 or ipv6 enabled".}
  {.error: "neither ipv4Enabled or ipv6Enabled defined".}

{.passC: "-DIPV6_FRAG_COPYHEADER=1".}


M src/taps/lwip/include/lwipopts.h => src/taps/lwip/include/lwipopts.h +1 -0
@@ 38,6 38,7 @@
#define SYS_DEBUG LWIP_DBG_OFF
#define SYS_LIGHTWEIGHT_PROT 0
#define TCP_DEBUG LWIP_DBG_OFF
#define TCP_MSS 1220 // Default for IPv6
#define TIMERS_DEBUG LWIP_DBG_OFF
#define UDP_DEBUG LWIP_DBG_OFF


M src/taps/lwip_implementation.nim => src/taps/lwip_implementation.nim +154 -95
@@ 3,9 3,6 @@

import lwip

when defined(solo5):
  import solo5/solo5

# *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
# Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)



@@ 15,7 12,9 @@ type Pcg32 = object
proc initPcg32*: Pcg32 =
  when defined(solo5):
    Pcg32(
      state: 0x853c49e6748fea9b'u64 xor uint64 solo5.clock_wall(),
      state: 0x853c49e6748fea9b'u64 xor
        solo5_clock_wall().uint64 xor
        solo5_clock_monotonic().uint64,
      inc: 0xda3e39cb94b95bdb'u64)
  elif defined(genode):
    Pcg32(


@@ 39,6 38,7 @@ type
var
  ERR_OK {.importc, nodecl.}: err_t
  ERR_VAL {.importc, nodecl.}: err_t
  ERR_RTE {.importc, nodecl.}: err_t
  ERR_WOULDBLOCK {.importc, nodecl.}: err_t

template isOk(e: err_t): bool = e == ERR_OK


@@ 107,7 107,6 @@ proc toLwipIp(ip: IpAddress): ip_addr_t =
        ip.address_v4[0], ip.address_v4[1], ip.address_v4[2], ip.address_v4[3])
    else:
      raiseAssert "IPv4 is disabled"
  assert result.toIpAddress == ip, $result.toIpAddress

type
  pbuf_layer {.importc, header: "lwip/pbuf.h".} = enum


@@ 134,10 133,7 @@ when ipv4Enabled:
when ipv6Enabled:
  type Netif_output_ip6 = proc (netif: pointer; p: Pbuf; ipaddr: ptr ip6_addr_t): err_t {.cdecl.}

when defined(solo5):
  discard

elif defined(genode):
when defined(genode):
  import std/tables, genode/constructibles

  type


@@ 186,15 182,15 @@ type
    name: string
    when defined(solo5):
      netif: Netif
      handle: devices.Handle
      handle: NetHandle
      info: NetInfo
      buf: seq[byte]
    elif defined(genode):
      heap: Heap
      nic: NicNetif

when defined(solo5):
  type NetifRegistry = HandleRegistry[TapsNetifRef]
elif defined(genode):

when defined(genode):
  proc netif(nic: NicNetif): ptr Netif {.importcpp: "&(#->lwip_netif())".}

var ethernet_input {.importc, nodecl, header: "netif/ethernet.h".}: Netif_input


@@ 253,20 249,22 @@ type
  TcpRecv = proc(arg: pointer; pcb: TcpPcb; p: Pbuf; err: err_t): err_t {.cdecl.}
  TcpSent = proc(arg: pointer; pcb: TcpPcb; len: uint16): err_t {.cdecl.}

proc tcp_new(): TcpPcb {.importc, tcpH.}
proc tcp_bind(pcb: TcpPcb, ipaddr: ptr ip_addr_t, port: uint16): err_t {.importc, tcpH.}
proc tcp_connect(pcb: TcpPcb, ipaddr: ptr ip_addr_t, port: uint16, connected: TcpConnected): err_t {.importc, tcpH.}
proc tcp_close(pcb: TcpPcb): err_t {.importc, tcpH.}
proc tcp_abort(pcb: TcpPcb) {.importc, tcpH.}
proc tcp_listen(pcb: TcpPcb): TcpPcb {.importc, tcpH.}
proc tcp_arg(pcb: TcpPcb; arg: pointer) {.importc, tcpH.}
proc tcp_accept(pcb: TcpPcb; accept: TcpAccept) {.importc, tcpH.}
proc tcp_arg(pcb: TcpPcb; arg: pointer) {.importc, tcpH.}
proc tcp_bind(pcb: TcpPcb, ipaddr: ptr ip_addr_t, port: uint16): err_t {.importc, tcpH.}
proc tcp_close(pcb: TcpPcb): err_t {.importc, tcpH.}
proc tcp_connect(pcb: TcpPcb, ipaddr: ptr ip_addr_t, port: uint16, connected: TcpConnected): err_t {.importc, tcpH.}
proc tcp_err(pcb: TcpPcb; err: TcpErr) {.importc, tcpH.}
proc tcp_listen(pcb: TcpPcb): TcpPcb {.importc, tcpH.}
proc tcp_new(): TcpPcb {.importc, tcpH.}
proc tcp_output(pcb: TcpPcb): err_t {.importc, tcpH.}
proc tcp_recv(pcb: TcpPcb; recv: TcpRecv) {.importc, tcpH.}
proc tcp_recved(pcb: TcpPcb; len: uint16) {.importc, tcpH.}
proc tcp_write(pcb: TcpPcb; arg: pointer; len: uint16; apiFlags: uint8): err_t {.importc, tcpH.}
proc tcp_sent(pcb: TcpPcb; sent: TcpSent) {.importc, tcpH.}
proc tcp_shutdown(pcb: TcpPcb; shut_rx, shut_tx: cint): err_t {.importc, tcpH.}
proc tcp_tcp_get_tcp_addrinfo(pcb: TcpPcb; local: cint; ipAddr: ptr ip_addr_t; port: ptr uint16): err_t {.importc, tcpH.}
proc tcp_write(pcb: TcpPcb; arg: pointer; len: uint16; apiFlags: uint8 = 0): err_t {.importc, tcpH.}

proc receiveBuffered(conn: Connection | ptr ConnectionObj) =
  assert(not conn.received.isNil)


@@ 291,28 289,36 @@ proc receiveBuffered(conn: Connection | ptr ConnectionObj) =
      ctx.remote = conn.remote
      conn.platform.recvPending = false
      tapsEcho "Connection -> Received<messageData, messageContext>"
      conn.received(buf, ctx)
      conn.receivedPartial(buf, ctx, conn.platform.remoteFinished)
      assert(buf.len < 0x1_00_00)
      tcp_recved(conn.platform.tcpPcb, uint16 buf.len)

proc tapsTcpError(arg: pointer; err: err_t) {.cdecl} =
  var conn = cast[ptr ConnectionObj](arg)
  assert not err.isOk
  conn.connectionError(err.toException)
  conn.callConnectionError(err.toException)

proc tapsTcpRecv(arg: pointer; pcb: TcpPcb; p: Pbuf; err: err_t): err_t {.cdecl} =
  var conn = cast[ptr ConnectionObj](arg)
  assert not conn.isNil
  assert err.isOk, "TODO: receiveError callback"
  if p.isNil:
    conn.platform.tcpPcb = nil
    result = tcp_close(pcb)
    if not conn.closed.isNil: conn.closed()
  if not err.isOk:
    var ctx: MessageContext # TODO
    conn.callReceiveError(ctx, newException(IOError, $err))
  elif p.isNil:
    conn.platform.remoteFinished = true
    if conn.platform.localFinished:
      checkErr tcp_close(pcb)
      conn.platform.pbuf = nil
    elif not conn.closed.isNil:
      conn.closed()
    else:
      checkErr tcp_close(pcb)
      conn.platform.pbuf = nil
  else:
    if conn.platform.pbuf.isNil: conn.platform.pbuf = p
    else: pbuf_cat(conn.platform.pbuf, p)
    if conn.platform.recvPending:
      assert not conn.received.isNil
      assert not conn.receivedPartial.isNil
      receiveBuffered(conn)

proc tapsTcpSent(arg: pointer; pcb: TcpPcb; len: uint16): err_t {.cdecl} =


@@ 354,13 360,21 @@ proc tapsTcpAccept(arg: pointer; newPcb: TcpPcb; err: err_t): err_t {.cdecl.} =

proc tapsTcpConnected(arg: pointer; pcb: TcpPcb; err: err_t): err_t {.cdecl.} =
  var conn = cast[ptr ConnectionObj](arg)
  assert not conn.isNil
  if err.isOk:
    tapsEcho "Connection -> Ready"
    conn.ready()
  if not conn.platform.tcpPcb.isNil:
    if err.isOk:
      # Close redundant TCP stream if Connection is ready.
      tcp_abort(pcb)
  else:
    tapsEcho "Connection -> InitiateError<reason?>"
    conn.initiateError(err.toException)
    if not err.isOk:
      tapsEcho "Connection -> InitiateError<reason?>"
      conn.callInitiateError(err.toException)
    else:
      tapsEcho "Connection -> Ready"
      conn.platform.tcpPcb = pcb
      tcp_err(conn.platform.tcpPcb, tapsTcpError)
      tcp_recv(conn.platform.tcpPcb, tapsTcpRecv)
      tcp_sent(conn.platform.tcpPcb, tapsTcpSent)
      conn.ready()

{.pragma: udpH, header: "lwip/udp.h".}



@@ 369,7 383,7 @@ proc udp_remove(pcb: UdpPcb) {.importc, udpH.}

when defined(solo5):
  type GlobalState = object
    netifs: NetifRegistry
    netifs: seq[TapsNetifRef]
    listeners: Deque[tuple[pc: Preconnection, ls: Listener]]

elif defined(genode):


@@ 379,11 393,19 @@ elif defined(genode):

var globalState: GlobalState

type IpAddrCallback* = proc (device: string; ip: IpAddress) {.closure.}
var ipAddrCallback: IpAddrCallback

proc onInterfaceUp*(cb: IpAddrCallback) = ipAddrCallback = cb

proc tapsStatusCallback(netif: ptr Netif) {.cdecl.} =
  when defined(solo5):
    var state = netif.state
    for ip in state.ipAddresses:
      echo state.name, " interface address ", ip
      if not ipAddrCallback.isNil:
        ipAddrCallback(state.name, ip)
      else:
        echo state.name, " interface address ", ip

proc tapsLinkOutput(netif: ptr Netif; p: Pbuf): err_t {.cdecl.} =
  when defined(genode):


@@ 391,16 413,21 @@ proc tapsLinkOutput(netif: ptr Netif; p: Pbuf): err_t {.cdecl.} =
  elif defined(solo5):
    var
      state = netif.state
      writeTotal: csize_t
      q = p
    result = ERR_OK
    while not q.isNil and result == ERR_OK and writeTotal < p.tot_len:
      result = case net_write(state.handle, cast[ptr uint8](q.payload), csize_t q.len)
        of SOLO5_R_OK: ERR_OK
        of SOLO5_R_AGAIN: ERR_WOULDBLOCK
        else: ERR_VAL
      writeTotal = writeTotal + csize_t q.len
      q = q.next
      res: Solo5Result
    if p.len == p.tot_len:
      res = solo5_net_write(state.handle, cast[ptr uint8](p.payload), csize_t p.len)
        # unchained buffer
    else:
      let n = int p.tot_len
      if state.buf.len < n:
        state.buf.setLen n
      if pbuf_copy_partial(p, addr state.buf[0], p.tot_len, 0) != p.tot_len:
        return ERR_VAL
      res = solo5_net_write(state.handle, addr state.buf[0], csize_t n)
    case res
      of SOLO5_R_OK: ERR_OK
      of SOLO5_R_AGAIN: ERR_WOULDBLOCK
      else: ERR_VAL
  else:
    {.error: "link output proc not implemented".}



@@ 430,6 457,7 @@ proc initTapsNetif(netif: ptr Netif): err_t {.cdecl.} =

when defined(solo5):
  import std/[endians, strformat]
  import solo5_dispatcher

  type Frame {.packed.} = object
    dst, src: MacAddress


@@ 440,30 468,39 @@ when defined(solo5):
    bigEndian16(addr t, unsafeAddr fr.etherType)
    fmt"""[{fr.dst}][{fr.src}][{t.toHex}]"""

  proc solo5NetHandler(h: Handle) =
    ## Handler invoked by asyncdispatcher to read a network packet.
    var state = globalState.netifs[h]
    var p = pbuf_alloc(PBUF_RAW, state.info.mtu.uint16, PBUF_POOL)
    var q = p
    var totRead: csize_t
    while not q.isNil:
  proc netInputLoop(state: TapsNetifRef; h: NetHandle) {.cps: Continuation.} =
    ## Continuation loop for the `h` net device.
    var p = pbuf_alloc(PBUF_RAW, state.info.mtu.uint16, PBUF_RAM)
    assert p.len == p.tot_len, "p.len:" & $p.len & " p.tot_len:" & $p.tot_len
    assert p.next.isNil
    while true:
      var readSize: csize_t
      if net_read(h, cast[ptr uint8](q.payload), q.len, addr readSize) != SOLO5_R_OK:
        q = nil
        pbuf_free(p)
      var res = solo5_net_read(h, cast[ptr uint8](p.payload), p.len, addr readSize)
      case res
      of SOLO5_R_AGAIN:
        await(h)
      of SOLO5_R_OK:
        assert readSize > 0
        pbuf_realloc(p, uint16 readSize)
        assert p.tot_len == readSize
        let res = state.netif.input(p, addr state.netif)
        if res != ERR_OK:
          pbuf_free(p)
          raise newException(IOError, $res)
        p = pbuf_alloc(PBUF_RAW, state.info.mtu.uint16, PBUF_RAM)
      else:
        totRead = totRead + readSize
        if readSize < q.len.csize_t: q = nil
        else: q = q.next
    pbuf_realloc(p, totRead.uint16)
    if totRead > 0 and state.netif.input(p, addr state.netif) == ERR_OK: discard
    else: pbuf_free(p)

  proc netAcquireHook*(name: string; h: Handle, ni: NetInfo) {.nimcall.} =
    var state = TapsNetifRef(name: name, handle: h, info: ni)
    globalState.netifs[h] = state
    registerHandler(h, solo5NetHandler)
        pbuf_free(p)
        raise newException(IOError, $res)

  proc netAcquireHook*(name: string; h: NetHandle, ni: NetInfo) {.nimcall.} =
    let state = TapsNetifRef(name: name, handle: h, info: ni)
    var i = int h
    if globalState.netifs.high < i:
      globalState.netifs.setLen(succ i)
    globalState.netifs[i] = state
    discard netif_add_noaddr(addr state.netif, addr(state[]), initTapsNetif)
    discard trampoline:
      whelp netInputLoop(state, h)

elif defined(genode):
  proc acquireNic*(env: GenodeEnvPtr; label = "") =


@@ 476,8 513,7 @@ elif defined(genode):

  # TODO: destructible Nics

proc sys_check_timeouts*() {.importc, header: "lwip/timeouts.h".}
  # TODO: do no export this, register it with the async dispatcher
proc sys_check_timeouts() {.importc, header: "lwip/timeouts.h".}

proc stop*(lis: Listener) =
  case lis.platform.transport


@@ 493,8 529,12 @@ proc stop*(lis: Listener) =
proc close*(conn: Connection) =
  case conn.platform.transport
  of lwipTcp:
    checkErr tcp_close(conn.platform.tcpPcb)
    conn.platform.tcpPcb = nil
    conn.platform.localFinished = true
    if conn.platform.remoteFinished:
      checkErr tcp_close(conn.platform.tcpPcb)
      conn.platform.tcpPcb = nil
    else:
      checkErr tcp_shutdown(conn.platform.tcpPcb, shut_rx=0, shut_tx=1)
  of lwipUdp:
    udp_remove(conn.platform.udpPcb)
    conn.platform.udpPcb = nil


@@ 515,23 555,30 @@ proc initiateUDP(preconn: Preconnection; result: Connection) =
  result.platform.udp_pcb = udp_new()
  raiseAssert "not implemented"

proc initiateTCP(preconn: Preconnection; conn: Connection) =
  # see ./lwip/upstream/src/apps/lwiperf/lwiperf.c:428
  conn.platform.tcp_pcb = tcp_new()
  tcp_arg(conn.platform.tcpPcb, addr conn[])
  tcp_err(conn.platform.tcpPcb, tapsTcpError)
  tcp_recv(conn.platform.tcpPcb, tapsTcpRecv)
  tcp_sent(conn.platform.tcpPcb, tapsTcpSent)
  var err: err_t
  for remote in preconn.remotes:
    var
when defined(solo5):

  proc initiateTCP(conn: Connection; remote: RemoteSpecifier) {.solo5dispatch.} =
    let
      pcb = tcp_new()
      ipAddr = remote.ip.toLwipIp
      port = remote.port.uint16
    err = tcp_connect(conn.platform.tcpPcb, addr ipAddr, port, tapsTcpConnected)
    if err.isOk:
      conn.remote = some remote
      break
  if not err.isOk: conn.initiateError(err.toException)
    tcp_arg(pcb, addr conn[])
    while conn.platform.tcpPcb.isNil:
      let err = tcp_connect(pcb, addr ipAddr, port, tapsTcpConnected)
      if err == ERR_OK:
        conn.remote = some remote
        return
      elif err == ERR_RTE:
        # Route not ready?
        yieldFor initDuration(seconds = 1)
    tcp_abort(pcb)
      # Already connected.

  proc initiateTCP(preconn: Preconnection; conn: Connection) =
    # see ./lwip/upstream/src/apps/lwiperf/lwiperf.c:428
    for remote in preconn.remotes:
      discard trampoline:
        whelp initiateTCP(conn, remote)

proc initiate*(preconn: var Preconnection; timeout = none(
    Duration)): Connection =


@@ 598,9 645,7 @@ proc send*(
    conn: Connection; msg: pointer; msgLen: int;
    ctx = MessageContext(); endOfMessage = true) =
  assert msgLen < 0x1_00_00
  var err = tcp_write(conn.platform.tcpPcb, msg, uint16 msgLen,
      TCP_WRITE_FLAG_COPY or (if endOfMessage: 0'u8 else: TCP_WRITE_FLAG_MORE))
    # TODO: no-copy, accept a seq[byte] and hold it until ACKed
  var err = tcp_write(conn.platform.tcpPcb, msg, uint16 msgLen, TCP_WRITE_FLAG_COPY)
  if err.isOk:
    ctx.len = msgLen
    conn.outgoing.addLast ctx


@@ 608,17 653,31 @@ proc send*(
  else:
    conn.callSendError(ctx, err.toException)

proc startBatch*(conn: Connection) =
  discard # nothing to do

proc endBatch*(conn: Connection) =
  if not conn.platform.tcpPcb.isNil:
    var err = tcp_output(conn.platform.tcpPcb)
      # Flush the buffered TCP pbuf.
    if not err.isOk:
      conn.callConnectionError(err.toException)

proc receive*(conn: Connection;
    minIncompleteLength = -1; maxLength = -1) =
  assert maxLength != 0
  (conn.platform.recvMinIncompleteLength, conn.platform.recvMaxLength) =
    (minIncompleteLength, maxLength)
  conn.platform.recvPending = true
  callSoon:
  if not conn.platform.pbuf.isNil:
    receiveBuffered(conn)

proc startBatch*(conn: Connection) = discard
proc endBatch*(conn: Connection) = discard

addTimer(initDuration(seconds=2), oneshot=false) do:
  sys_check_timeouts()
when defined(solo5):
  proc checkTimeouts() {.solo5dispatch.} =
    const period = initDuration(milliseconds = 500)
    while true:
      yieldFor(period)
      sys_check_timeouts()

  discard trampoline:
    whelp checkTimeouts()

M src/taps/lwip_types.nim => src/taps/lwip_types.nim +2 -1
@@ 2,7 2,7 @@
# SPDX-License-Identifier: Unlicense

when defined(solo5):
  import solo5/devices
  import solo5

type
  Pbuf {.importc: "struct pbuf", header: "lwip/pbuf.h".} = ptr object


@@ 29,6 29,7 @@ type
      tcpPcb: TcpPcb
      pbuf: Pbuf
      pbufOff: uint16
      localFinished, remoteFinished: bool
    of lwipUdp:
      udpPcb: UdpPcb
    recvMinIncompleteLength, recvMaxLength: int

M taps.nimble => taps.nimble +2 -2
@@ 1,6 1,6 @@
# Package

version = "20240318"
version = "20240402"
author        = "Emery Hemingway"
description   = "Transport Services Interface"
license       = "Unlicense"


@@ 10,4 10,4 @@ srcDir        = "src"

# Dependencies

requires "nim >= 2.0.0", "https://git.sr.ht/~ehmry/getdns-nim >= 20220928", "https://github.com/alaviss/nim-sys.git >= 0.0.4", "https://github.com/ehmry/cps#c90530ac57f98a842b7be969115c6ef08bdcc564", "https://git.sr.ht/~ehmry/getdns-nim"
requires "nim >= 2.0.0", "https://git.sr.ht/~ehmry/getdns-nim >= 20220928", "https://github.com/alaviss/nim-sys.git >= 0.0.4", "https://github.com/ehmry/cps#c90530ac57f98a842b7be969115c6ef08bdcc564", "https://git.sr.ht/~ehmry/getdns-nim", "https://git.sr.ht/~ehmry/solo5_dispatcher"

A tests/Tupfile => tests/Tupfile +4 -0
@@ 0,0 1,4 @@
include_rules

# : solo5*test.nim |> !nim_solo5_hvt |>
: solo5*test.nim | ../<sources> |> !nim_solo5_spt |>

A tests/config.nims => tests/config.nims +1 -0
@@ 0,0 1,1 @@
switch("path", "$projectDir/../src")

A tests/solo5_test.nim => tests/solo5_test.nim +44 -0
@@ 0,0 1,44 @@
# SPDX-FileCopyrightText: ☭ Emery Hemingway
# SPDX-License-Identifier: Unlicense

import std/options
import taps
import solo5, solo5_dispatcher

proc `$`(b: seq[byte]): string = cast[string](b)

proc connectionHandler(conn: Connection) =
  echo "Received new Connection."
  conn.onClosed do ():
    conn.close()
  conn.onReceivedPartial do (data: seq[byte]; ctx: MessageContext; eom: bool):
    echo "Received partial message of ", data.len, " bytes"
    conn.send(data)
    conn.receive()
  conn.onSent do (ctx: MessageContext):
    echo "Sent cb received, message ", ctx, " has been sent."
  conn.onReceiveError do (ctx: MessageContext; reason: ref Exception):
    echo "connection error: ", reason.msg
  conn.receive()

proc main =
  var lp = newLocalEndpoint()
  lp.with Port(1024)

  var tp = newTransportProperties()
  tp.require "reliability"
  tp.ignore "congestion-control"
  tp.ignore "preserve-order"

  let preconn = newPreconnection(
    local=[lp], transport=tp.some)
  var listener = preconn.listen()
  listener.onListenError do (err: ref Exception):
    echo "Listen Error occcured, ", err.msg, "."
    quit -1
  listener.onConnectionReceived(connectionHandler)

  run()

acquireDevices([("echoserver", netBasic)], netAcquireHook)
main()

A tests/solo5_test.nim.cfg => tests/solo5_test.nim.cfg +1 -0
@@ 0,0 1,1 @@
define:ipv6Enabled