mirror of
https://github.com/logos-messaging/nim-ffi.git
synced 2026-06-21 08:49:34 +00:00
165 lines
4.4 KiB
Nim
165 lines
4.4 KiB
Nim
## {.ffiHandle.} round-trip through the full C-shape dispatch path: a handle
|
|
## returned by one `.ffi.` proc crosses as a uint64 and is reconstituted as the
|
|
## live object by another; stale/forged/null ids miss cleanly with RET_ERR.
|
|
|
|
import std/[locks, strutils]
|
|
import unittest2
|
|
import results
|
|
import ffi
|
|
|
|
type HandleLib = object
|
|
base: int
|
|
|
|
# Stub the dylib NimMain importc that declareLibrary emits (this links as a plain exe).
|
|
{.emit: "void libhandletestNimMain(void) {}".}
|
|
|
|
declareLibrary("handletest", HandleLib)
|
|
|
|
type Session {.ffiHandle.} = ref object
|
|
token: string
|
|
hits: int
|
|
|
|
type OpenReq {.ffi.} = object
|
|
name: string
|
|
|
|
proc handletest_open*(
|
|
lib: HandleLib, req: OpenReq
|
|
): Future[Result[Session, string]] {.ffi.} =
|
|
return ok(Session(token: req.name & ":" & $lib.base, hits: 0))
|
|
|
|
proc handletest_token*(
|
|
lib: HandleLib, s: Session
|
|
): Future[Result[string, string]] {.ffi.} =
|
|
s.hits.inc()
|
|
return ok(s.token & "#" & $s.hits)
|
|
|
|
# Handle as the receiver (first param) — lib type comes from declareLibrary.
|
|
proc handletest_session_bump*(s: Session): Future[Result[int, string]] {.ffi.} =
|
|
s.hits.inc()
|
|
return ok(s.hits)
|
|
|
|
type CallbackData = object
|
|
lock: Lock
|
|
cond: Cond
|
|
called: bool
|
|
retCode: cint
|
|
msg: array[1024, byte]
|
|
msgLen: int
|
|
|
|
proc initCallbackData(d: var CallbackData) =
|
|
d.lock.initLock()
|
|
d.cond.initCond()
|
|
|
|
proc deinitCallbackData(d: var CallbackData) =
|
|
d.cond.deinitCond()
|
|
d.lock.deinitLock()
|
|
|
|
proc testCallback(
|
|
retCode: cint, msg: ptr cchar, len: csize_t, userData: pointer
|
|
) {.cdecl, gcsafe, raises: [].} =
|
|
let d = cast[ptr CallbackData](userData)
|
|
acquire(d[].lock)
|
|
d[].retCode = retCode
|
|
let n = min(int(len), d[].msg.len)
|
|
if n > 0 and not msg.isNil:
|
|
copyMem(addr d[].msg[0], msg, n)
|
|
d[].msgLen = n
|
|
d[].called = true
|
|
signal(d[].cond)
|
|
release(d[].lock)
|
|
|
|
proc waitCallback(d: var CallbackData) =
|
|
acquire(d.lock)
|
|
while not d.called:
|
|
wait(d.cond, d.lock)
|
|
release(d.lock)
|
|
|
|
proc payload(d: var CallbackData): seq[byte] =
|
|
var b = newSeq[byte](d.msgLen)
|
|
if d.msgLen > 0:
|
|
copyMem(addr b[0], addr d.msg[0], d.msgLen)
|
|
b
|
|
|
|
proc text(d: var CallbackData): string =
|
|
var s = newString(d.msgLen)
|
|
if d.msgLen > 0:
|
|
copyMem(addr s[0], addr d.msg[0], d.msgLen)
|
|
s
|
|
|
|
proc encodedPtr(b: var seq[byte]): ptr byte =
|
|
if b.len == 0:
|
|
nil
|
|
else:
|
|
cast[ptr byte](addr b[0])
|
|
|
|
template runCall(d, ctx, reqBytes, exportProc) =
|
|
## Fires `exportProc` with `reqBytes` and blocks until the callback lands in `d`.
|
|
initCallbackData(d)
|
|
var rb = reqBytes
|
|
check exportProc(ctx, testCallback, addr d, encodedPtr(rb), rb.len.csize_t) == RET_OK
|
|
waitCallback(d)
|
|
|
|
suite "{.ffiHandle.} round-trip":
|
|
setup:
|
|
let ctx {.inject.} = HandleLibFFIPool.createFFIContext().get()
|
|
|
|
teardown:
|
|
discard HandleLibFFIPool.destroyFFIContext(ctx)
|
|
|
|
test "handle returned as uint64, reconstituted on the next call":
|
|
var od: CallbackData
|
|
runCall(
|
|
od,
|
|
ctx,
|
|
cborEncode(HandletestOpenReq(req: OpenReq(name: "alpha"))),
|
|
handletest_open,
|
|
)
|
|
defer:
|
|
deinitCallbackData(od)
|
|
check od.retCode == RET_OK
|
|
let handle = cborDecode(payload(od), uint64).value
|
|
check handle == 1'u64 # ids start at 1
|
|
|
|
var td: CallbackData
|
|
runCall(td, ctx, cborEncode(HandletestTokenReq(s: handle)), handletest_token)
|
|
defer:
|
|
deinitCallbackData(td)
|
|
check td.retCode == RET_OK
|
|
check cborDecode(payload(td), string).value == "alpha:0#1"
|
|
|
|
test "handle as receiver (first param)":
|
|
var od: CallbackData
|
|
runCall(
|
|
od,
|
|
ctx,
|
|
cborEncode(HandletestOpenReq(req: OpenReq(name: "beta"))),
|
|
handletest_open,
|
|
)
|
|
defer:
|
|
deinitCallbackData(od)
|
|
let handle = cborDecode(payload(od), uint64).value
|
|
|
|
var bd: CallbackData
|
|
runCall(
|
|
bd, ctx, cborEncode(HandletestSessionBumpReq(s: handle)), handletest_session_bump
|
|
)
|
|
defer:
|
|
deinitCallbackData(bd)
|
|
check bd.retCode == RET_OK
|
|
check cborDecode(payload(bd), int).value == 1
|
|
|
|
test "forged handle misses cleanly with RET_ERR":
|
|
var td: CallbackData
|
|
runCall(td, ctx, cborEncode(HandletestTokenReq(s: 9999'u64)), handletest_token)
|
|
defer:
|
|
deinitCallbackData(td)
|
|
check td.retCode == RET_ERR
|
|
check "ffiHandle" in text(td)
|
|
|
|
test "null handle (0) misses with RET_ERR":
|
|
var td: CallbackData
|
|
runCall(td, ctx, cborEncode(HandletestTokenReq(s: 0'u64)), handletest_token)
|
|
defer:
|
|
deinitCallbackData(td)
|
|
check td.retCode == RET_ERR
|