Add nph formating

This commit is contained in:
Arnaud 2026-05-19 14:15:26 +04:00
parent f7993b02bf
commit 5a3600278f
No known key found for this signature in database
GPG Key ID: A6C7C781817146FA
6 changed files with 126 additions and 90 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
vendor/libplum/build/
tests/test_plum
nimcache/
.git/

View File

@ -111,6 +111,14 @@ Three env vars control verbosity:
TEST_VERBOSE=1 MINIUPNPD_VERBOSE=1 LIBPLUM_VERBOSE=1 nimble testIntegration
```
## Development
Format the code with [nph](https://github.com/arnetheduck/nph):
```bash
nimble format
```
## License
Licensed and distributed under either of

View File

@ -19,6 +19,9 @@ proc compileStaticLibraries() =
exec("cmake --build build")
cpFile("build/libplum.a", "libplum.a")
task format, "format Nim code using nph":
exec "nph libplum/ tests/"
task buildBundledLibs, "build bundled libraries":
compileStaticLibraries()

View File

@ -19,11 +19,13 @@ const
# Nim has no equivalent, so the generated C code drops the `const`, causing
# a type mismatch warning in GCC 15+. This pragma suppresses that warning
# only in this translation unit and is valid for both C and C++.
{.emit: """
{.
emit: """
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wno-incompatible-pointer-types"
#endif
""".}
"""
.}
when defined(windows):
{.passl: "-lws2_32 -liphlpapi -lbcrypt".}
@ -31,69 +33,74 @@ else:
{.passl: "-lpthread".}
const
PLUM_ERR_SUCCESS* = cint(0)
PLUM_ERR_INVALID* = cint(-1)
PLUM_ERR_FAILED* = cint(-2)
PLUM_ERR_SUCCESS* = cint(0)
PLUM_ERR_INVALID* = cint(-1)
PLUM_ERR_FAILED* = cint(-2)
PLUM_ERR_NOT_AVAIL* = cint(-3)
PLUM_MAX_HOST_LEN* = 256
PLUM_MAX_HOST_LEN* = 256
PLUM_MAX_ADDRESS_LEN* = 64
# Import plum enums with cint size
type
plum_log_level_t* {.importc: "plum_log_level_t", header: "plum.h",
size: sizeof(cint).} = enum
plum_log_level_t* {.
importc: "plum_log_level_t", header: "plum.h", size: sizeof(cint)
.} = enum
PLUM_LOG_LEVEL_VERBOSE = 0
PLUM_LOG_LEVEL_DEBUG = 1
PLUM_LOG_LEVEL_INFO = 2
PLUM_LOG_LEVEL_WARN = 3
PLUM_LOG_LEVEL_ERROR = 4
PLUM_LOG_LEVEL_FATAL = 5
PLUM_LOG_LEVEL_NONE = 6
PLUM_LOG_LEVEL_DEBUG = 1
PLUM_LOG_LEVEL_INFO = 2
PLUM_LOG_LEVEL_WARN = 3
PLUM_LOG_LEVEL_ERROR = 4
PLUM_LOG_LEVEL_FATAL = 5
PLUM_LOG_LEVEL_NONE = 6
plum_ip_protocol_t* {.importc: "plum_ip_protocol_t", header: "plum.h",
size: sizeof(cint).} = enum
plum_ip_protocol_t* {.
importc: "plum_ip_protocol_t", header: "plum.h", size: sizeof(cint)
.} = enum
PLUM_IP_PROTOCOL_TCP = 0
PLUM_IP_PROTOCOL_UDP = 1
plum_state_t* {.importc: "plum_state_t", header: "plum.h",
size: sizeof(cint).} = enum
PLUM_STATE_DESTROYED = 0
PLUM_STATE_PENDING = 1
PLUM_STATE_SUCCESS = 2
PLUM_STATE_FAILURE = 3
plum_state_t* {.importc: "plum_state_t", header: "plum.h", size: sizeof(cint).} = enum
PLUM_STATE_DESTROYED = 0
PLUM_STATE_PENDING = 1
PLUM_STATE_SUCCESS = 2
PLUM_STATE_FAILURE = 3
PLUM_STATE_DESTROYING = 4
plum_mapping_protocol_t* {.importc: "plum_mapping_protocol_t", header: "plum.h",
size: sizeof(cint).} = enum
plum_mapping_protocol_t* {.
importc: "plum_mapping_protocol_t", header: "plum.h", size: sizeof(cint)
.} = enum
PLUM_MAPPING_PROTOCOL_UNKNOWN = 0
PLUM_MAPPING_PROTOCOL_PCP = 1
PLUM_MAPPING_PROTOCOL_NATPMP = 2
PLUM_MAPPING_PROTOCOL_UPNP = 3
PLUM_MAPPING_PROTOCOL_DIRECT = 4
PLUM_MAPPING_PROTOCOL_PCP = 1
PLUM_MAPPING_PROTOCOL_NATPMP = 2
PLUM_MAPPING_PROTOCOL_UPNP = 3
PLUM_MAPPING_PROTOCOL_DIRECT = 4
# Define the callback to receive the plum logs
plum_log_callback_t* = proc(level: plum_log_level_t, message: cstring) {.cdecl.}
# Define the config struct, passed by copy (usual for struct).
plum_config_t* {.importc: "plum_config_t", header: "plum.h", bycopy.} = object
log_level* {.importc: "log_level".}: plum_log_level_t
log_callback* {.importc: "log_callback".}: plum_log_callback_t
dummytls_domain* {.importc: "dummytls_domain".}: cstring
discover_timeout* {.importc: "discover_timeout".}: cint # msecs, 0 means use default (10000)
mapping_timeout* {.importc: "mapping_timeout".}: cint # msecs, 0 means use default (10000)
recheck_period* {.importc: "recheck_period".}: cint # msecs, 0 means use default (300000)
log_level* {.importc: "log_level".}: plum_log_level_t
log_callback* {.importc: "log_callback".}: plum_log_callback_t
dummytls_domain* {.importc: "dummytls_domain".}: cstring
discover_timeout* {.importc: "discover_timeout".}: cint
# msecs, 0 means use default (10000)
mapping_timeout* {.importc: "mapping_timeout".}: cint
# msecs, 0 means use default (10000)
recheck_period* {.importc: "recheck_period".}: cint
# msecs, 0 means use default (300000)
# Define the mapping struct, passed by copy (usual for struct).
# The user_ptr is a pointer to the MappingHandle in order to receive the result
plum_mapping_t* {.importc: "plum_mapping_t", header: "plum.h", bycopy.} = object
protocol* {.importc: "protocol".}: plum_ip_protocol_t
protocol* {.importc: "protocol".}: plum_ip_protocol_t
mapping_protocol* {.importc: "mapping_protocol".}: plum_mapping_protocol_t
internal_port* {.importc: "internal_port".}: uint16
external_port* {.importc: "external_port".}: uint16
external_host* {.importc: "external_host".}: array[PLUM_MAX_HOST_LEN, char]
user_ptr* {.importc: "user_ptr".}: pointer
internal_port* {.importc: "internal_port".}: uint16
external_port* {.importc: "external_port".}: uint16
external_host* {.importc: "external_host".}: array[PLUM_MAX_HOST_LEN, char]
user_ptr* {.importc: "user_ptr".}: pointer
# Define the callback to receive the mapping result
plum_mapping_callback_t* =
@ -101,22 +108,24 @@ type
# Import plum functions
proc plum_init*(config: ptr plum_config_t): cint
{.importc: "plum_init", header: "plum.h".}
proc plum_init*(
config: ptr plum_config_t
): cint {.importc: "plum_init", header: "plum.h".}
proc plum_cleanup*(): cint
{.importc: "plum_cleanup", header: "plum.h".}
proc plum_cleanup*(): cint {.importc: "plum_cleanup", header: "plum.h".}
proc plum_create_mapping*(mapping: ptr plum_mapping_t,
callback: plum_mapping_callback_t): cint
{.importc: "plum_create_mapping", header: "plum.h".}
proc plum_create_mapping*(
mapping: ptr plum_mapping_t, callback: plum_mapping_callback_t
): cint {.importc: "plum_create_mapping", header: "plum.h".}
proc plum_query_mapping*(id: cint, state: ptr plum_state_t,
mapping: ptr plum_mapping_t): cint
{.importc: "plum_query_mapping", header: "plum.h".}
proc plum_query_mapping*(
id: cint, state: ptr plum_state_t, mapping: ptr plum_mapping_t
): cint {.importc: "plum_query_mapping", header: "plum.h".}
proc plum_destroy_mapping*(id: cint): cint
{.importc: "plum_destroy_mapping", header: "plum.h".}
proc plum_destroy_mapping*(
id: cint
): cint {.importc: "plum_destroy_mapping", header: "plum.h".}
proc plum_get_local_address*(buffer: cstring, size: csize_t): cint
{.importc: "plum_get_local_address", header: "plum.h".}
proc plum_get_local_address*(
buffer: cstring, size: csize_t
): cint {.importc: "plum_get_local_address", header: "plum.h".}

View File

@ -12,11 +12,13 @@ import chronos/threadsync
import results
import ./libplum
{.emit: """
{.
emit: """
#ifdef __GNUC__
#pragma GCC diagnostic ignored "-Wincompatible-pointer-types"
#endif
""".}
"""
.}
export results
@ -28,18 +30,18 @@ type
UDP = PLUM_IP_PROTOCOL_UDP.int
PlumState* = enum
Destroyed = PLUM_STATE_DESTROYED.int
Pending = PLUM_STATE_PENDING.int
Success = PLUM_STATE_SUCCESS.int
Failure = PLUM_STATE_FAILURE.int
Destroyed = PLUM_STATE_DESTROYED.int
Pending = PLUM_STATE_PENDING.int
Success = PLUM_STATE_SUCCESS.int
Failure = PLUM_STATE_FAILURE.int
Destroying = PLUM_STATE_DESTROYING.int
MappingProtocol* = enum
Unknown = PLUM_MAPPING_PROTOCOL_UNKNOWN.int
PCP = PLUM_MAPPING_PROTOCOL_PCP.int
NatPmp = PLUM_MAPPING_PROTOCOL_NATPMP.int
UPnP = PLUM_MAPPING_PROTOCOL_UPNP.int
Direct = PLUM_MAPPING_PROTOCOL_DIRECT.int
PCP = PLUM_MAPPING_PROTOCOL_PCP.int
NatPmp = PLUM_MAPPING_PROTOCOL_NATPMP.int
UPnP = PLUM_MAPPING_PROTOCOL_UPNP.int
Direct = PLUM_MAPPING_PROTOCOL_DIRECT.int
PlumMapping* = object
protocol*: PlumProtocol
@ -52,8 +54,7 @@ type
id*: cint
mapping*: PlumMapping
PlumStateCallback* =
proc(state: PlumState, mapping: PlumMapping) {.callback.}
PlumStateCallback* = proc(state: PlumState, mapping: PlumMapping) {.callback.}
MappingHandle = ref object
signal: ThreadSignalPtr
@ -76,9 +77,11 @@ type
# libplum calls mappingCallback from its own C thread. Under refc, any thread
# that touches Nim objects must register with the GC first.
template foreignThreadGc(body: untyped) =
when declared(setupForeignThreadGc): setupForeignThreadGc()
when declared(setupForeignThreadGc):
setupForeignThreadGc()
body
when declared(tearDownForeignThreadGc): tearDownForeignThreadGc()
when declared(tearDownForeignThreadGc):
tearDownForeignThreadGc()
var activeMappingsLock: Lock
var activeMappings {.guard: activeMappingsLock.}: Table[cint, MappingHandle]
@ -92,8 +95,9 @@ template withSafeLock(body: untyped) =
withLock activeMappingsLock:
body
proc mappingCallback(id: cint, state: plum_state_t,
raw: ptr plum_mapping_t) {.cdecl, raises: [].} =
proc mappingCallback(
id: cint, state: plum_state_t, raw: ptr plum_mapping_t
) {.cdecl, raises: [].} =
## Called from libplum's internal C thread on SUCCESS, FAILURE, and DESTROYED.
foreignThreadGc:
@ -139,7 +143,7 @@ proc mappingCallback(id: cint, state: plum_state_t,
mappingProtocol: MappingProtocol(raw[].mapping_protocol.int),
internalPort: raw[].internal_port,
externalPort: raw[].external_port,
externalHost: $cast[cstring](addr raw[].external_host)
externalHost: $cast[cstring](addr raw[].external_host),
)
handle.onStateChange(plumState, mapping)
@ -147,7 +151,7 @@ proc init*(
logLevel: plum_log_level_t = PLUM_LOG_LEVEL_NONE,
discoverTimeout: int = 0,
mappingTimeout: int = 0,
recheckPeriod: int = 0
recheckPeriod: int = 0,
): Result[void, string] {.raises: [].} =
## init MUST be called to setup internal plum thread (plum_init).
@ -157,7 +161,7 @@ proc init*(
dummytls_domain: nil,
discover_timeout: discoverTimeout.cint,
mapping_timeout: mappingTimeout.cint,
recheck_period: recheckPeriod.cint
recheck_period: recheckPeriod.cint,
)
let res = plum_init(addr config)
@ -180,21 +184,18 @@ proc createMapping*(
internalPort: uint16,
externalPort: uint16 = 0,
timeout: Duration = seconds(30),
onStateChange: PlumStateCallback = nil
onStateChange: PlumStateCallback = nil,
): Future[Result[MappingResult, string]] {.async: (raises: [CancelledError]).} =
let signal = ThreadSignalPtr.new().valueOr:
return err("plum: cannot create signal: " & $error)
let handle = MappingHandle(
signal: signal,
onStateChange: onStateChange
)
let handle = MappingHandle(signal: signal, onStateChange: onStateChange)
var req = plum_mapping_t(
protocol: plum_ip_protocol_t(protocol.int),
internal_port: internalPort,
external_port: externalPort,
user_ptr: cast[pointer](handle)
user_ptr: cast[pointer](handle),
)
# Avoid issue with refc.
@ -252,7 +253,7 @@ proc createMapping*(
mappingProtocol: h.resolvedMappingProtocol,
internalPort: h.resolvedInternalPort,
externalPort: h.resolvedExternalPort,
externalHost: $cast[cstring](unsafeAddr h.resolvedExternalHost)
externalHost: $cast[cstring](unsafeAddr h.resolvedExternalHost),
)
if resolvedState == Success:

View File

@ -41,14 +41,20 @@ const miniupnp_protocol {.strdefine.} = ""
# must fall back to NAT-PMP on its own (tests the silent-timeout fallback fix).
when miniupnp_protocol != "":
const expectedMappingProtocol =
when miniupnp_protocol == "pcp": PCP
elif miniupnp_protocol == "natpmp": NatPmp
else: UPnP
when miniupnp_protocol == "pcp":
PCP
elif miniupnp_protocol == "natpmp":
NatPmp
else:
UPnP
suite "plum - " & miniupnp_protocol & " using miniupnp":
test "createMapping TCP and destroyMapping":
let logLevel = if getEnv("LIBPLUM_VERBOSE") == "1": PLUM_LOG_LEVEL_VERBOSE
else: PLUM_LOG_LEVEL_NONE
let logLevel =
if getEnv("LIBPLUM_VERBOSE") == "1":
PLUM_LOG_LEVEL_VERBOSE
else:
PLUM_LOG_LEVEL_NONE
check init(discoverTimeout = 15000, logLevel = logLevel).isOk()
let r = waitFor createMapping(TCP, 8101, timeout = seconds(40))
@ -60,14 +66,18 @@ when miniupnp_protocol != "":
check res.mapping.mappingProtocol == expectedMappingProtocol
check hasMapping(res.id)
if getEnv("TEST_VERBOSE") == "1":
echo miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort
echo miniupnp_protocol & " TCP: " & res.mapping.externalHost & ":" &
$res.mapping.externalPort
destroyMapping(res.id)
discard cleanup()
test "createMapping UDP and destroying":
let logLevel = if getEnv("LIBPLUM_VERBOSE") == "1": PLUM_LOG_LEVEL_VERBOSE
else: PLUM_LOG_LEVEL_NONE
let logLevel =
if getEnv("LIBPLUM_VERBOSE") == "1":
PLUM_LOG_LEVEL_VERBOSE
else:
PLUM_LOG_LEVEL_NONE
check init(discoverTimeout = 2000, logLevel = logLevel).isOk()
let r = waitFor createMapping(UDP, 8090, timeout = seconds(40))
@ -77,7 +87,8 @@ when miniupnp_protocol != "":
check res.mapping.externalPort > 0
check res.mapping.mappingProtocol == expectedMappingProtocol
if getEnv("TEST_VERBOSE") == "1":
echo miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" & $res.mapping.externalPort
echo miniupnp_protocol & " UDP: " & res.mapping.externalHost & ":" &
$res.mapping.externalPort
destroyMapping(res.id)
discard cleanup()