libtorrent integration and Fennel binding
This document describes the libtorrent integration used by Space today. It is the authoritative reference for the libtorrent Fennel module exposed by src/lua_libtorrent.cpp.
overview
Space binds libtorrent-rasterbar into Lua/Fennel through package.preload["libtorrent"]. The binding is implemented in C++ and exposed as hyphen-case APIs.
Design goals:
- expose a practical, script-friendly control surface for torrent/session operations
- keep data model explicit (
SessionHandle,AddTorrentParamsHandle,SessionParamsHandle) - support both offline and online integration tests
- fail loudly when a capability is unavailable
build and availability
The module is conditionally compiled:
- enabled build: real libtorrent implementation is exported
- disabled build: module still exists with
available = falseand all API calls throw a clear error
Runtime fields:
libtorrent.available-> booleanlibtorrent.missing-reason-> string ornil
Missing dependency message:
libtorrent unavailable (install libtorrent-rasterbar-dev and rebuild)
naming conventions
All exposed API names are hyphen-case. Examples:
parse-magnet-uriwait-for-alertsave-state-flags
No snake_case aliases are intentionally provided except explicit compatibility helpers we expose (for example parse-magnet-uri-dict).
object model
SessionHandle
Created by:
(libtorrent.Session opts)(libtorrent.session [opts])
Lifecycle:
dropis-closed
Primary responsibilities:
- add/remove/find torrents
- session/torrent controls
- DHT operations
- alert polling
- status and completion waiting
AddTorrentParamsHandle
Created by parsing/loading helpers, for example:
parse-magnet-uri-paramsload-torrent-file-paramsload-torrent-buffer-paramsload-torrent-parsedread-resume-data/read-resume-data-params
Primary responsibilities:
- mutable add-torrent parameters
- serialization to resume/torrent buffers
- table inspection with
to-table
SessionParamsHandle
Created by:
session-paramsread-session-paramsses:session-state
Primary responsibilities:
- session bootstrap settings/ext-state
- session params serialization roundtrip
top-level module api
factory and utility
Session(opts)session([opts])session-params([opts])version()
torrent metadata and magnet
create-torrent(opts)parse-magnet-uri(uri)parse-magnet-uri-dict(uri)make-magnet-uri(torrent-path)make-magnet-uri-from-params(params-handle)load-torrent-file(path)load-torrent-buffer(buffer)
params/serialization helpers
parse-magnet-uri-params(uri)load-torrent-file-params(path)load-torrent-buffer-params(buffer)load-torrent-parsed(parsed-table)read-resume-data(buffer)read-resume-data-params(buffer)write-resume-data(params-handle)write-resume-data-buf(params-handle)write-torrent-file(params-handle)write-torrent-file-buf(params-handle)read-session-params(buffer[, flags])write-session-params(session-params-handle[, flags])write-session-params-buf(session-params-handle[, flags])
bencode helpers
bencode(value)bdecode(buffer)
settings/metrics presets
default-settings()high-performance-seed()min-memory-usage()session-stats-metrics()find-metric-idx(name)
ed25519 / mutable item signing helpers
ed25519-create-seed()ed25519-create-keypair(seed-hex)sign-mutable-item(opts)verify-mutable-item(opts)
operation/error helpers
operation-name(op-code)libtorrent-category-name()http-category-name()socks-category-name()upnp-category-name()i2p-category-name()system-category-name()generic-category-name()bdecode-category-name()
constants tables
torrent-flagsstorage-modessave-state-flagsdht-announce-flags
SessionHandle api summary
add/remove/find
add-torrent-file(opts)add-info-hash(opts)add-magnet-uri(uri[, opts])add-torrent-params(opts)get-torrents()find-torrent(info-hash-hex)remove-torrent(handle-id[, opts])
per-torrent control
pause-torrent(handle-id)resume-torrent(handle-id)set-torrent-download-limit(handle-id, value)/torrent-download-limit(handle-id)set-torrent-upload-limit(handle-id, value)/torrent-upload-limit(handle-id)set-torrent-max-connections(handle-id, value)/torrent-max-connections(handle-id)set-torrent-max-uploads(handle-id, value)/torrent-max-uploads(handle-id)set-torrent-piece-priorities(handle-id, priorities)/torrent-piece-priorities(handle-id)set-torrent-file-priorities(handle-id, priorities)/torrent-file-priorities(handle-id)force-reannounce(handle-id)force-dht-announce(handle-id)status(handle-id)torrent-info(handle-id)make-magnet-uri(handle-id)wait-for-complete(handle-id[, opts])
session control/settings
apply-settings(settings)get-settings()pause()/resume()/is-paused()session-status()session-state([flags])
alerts
set-alert-mask(mask)set-alert-queue-size-limit(limit)wait-for-alert(timeout-ms)pop-alerts()post-session-stats()post-dht-stats()post-torrent-updates()
DHT operations
start-dht()/stop-dht()/is-dht-running()add-dht-node({:host ... :port ...})add-dht-router({:host ... :port ...})dht-get-peers(info-hash-hex)dht-announce({:info-hash ... :port ... :flags ...})dht-live-nodes(node-id-hex)dht-sample-infohashes({:host ... :port ... :target ...})dht-get-item(target-hex)dht-put-item(item-table)-> target hexdht-get-mutable-item({:public-key ... [:salt ...]})dht-put-mutable-item({:public-key ... :secret-key ... [:salt ...] :seq ... :item ...})dht-direct-request({:host ... :port ... :request ... [:userdata ...]})
alert table enrichment
All alerts include base fields:
type,message,category,alert-type,timestamp-ms
The binding enriches several alert families with additional keys (when present), including:
- add/session/state alerts
- DHT stats alerts
- immutable/mutable DHT item alerts
- DHT put alerts
- DHT get-peers/live-nodes/sample-infohashes replies
- DHT direct response alerts
Callers should treat optional fields as optional and type-check before use. dht-direct-response-alert may include userdata when :userdata was set on dht-direct-request.
testing strategy
Tests are implemented in Fennel modules under assets/lua/tests/.
Primary suites:
tests.test-libtorrent-offline:main(fast enough for regular runs, no network requirement)tests.test-libtorrent-online:main(real network roundtrip; intentionally slow)tests.test-libtorrent:main(combined; includes online only whenSPACE_LIBTORRENT_INCLUDE_ONLINE=1)
Typical commands:
SKIP_KEYRING_TESTS=1 XDG_DATA_HOME=/tmp/space/tests/xdg-data SPACE_DISABLE_AUDIO=1 SPACE_ASSETS_PATH=$(pwd)/assets ./build/space -m tests.test-libtorrent-offline:mainSKIP_KEYRING_TESTS=1 XDG_DATA_HOME=/tmp/space/tests/xdg-data SPACE_DISABLE_AUDIO=1 SPACE_ASSETS_PATH=$(pwd)/assets ./build/space -m tests.test-libtorrent-online:mainSKIP_KEYRING_TESTS=1 XDG_DATA_HOME=/tmp/space/tests/xdg-data SPACE_LIBTORRENT_INCLUDE_ONLINE=1 SPACE_DISABLE_AUDIO=1 SPACE_ASSETS_PATH=$(pwd)/assets ./build/space -m tests.test-libtorrent:main
known limitations
verify-mutable-itemis not available in this environment's libtorrent binary (symbol declared in headers but not exported by the installed package). The binding throws a clear runtime error.add-dht-routeris ABI-dependent and throws a clear runtime error when unavailable in the active libtorrent ABI.
torrent cli tools
Space includes two Fennel CLI tools built on this binding:
tools.torrent-downloadtools.torrent-upload
tools.torrent-download
Purpose:
- download from magnet URI, v1 info-hash, or
.torrentfile - continue seeding until interrupted
- resume across restarts
Run:
SPACE_DISABLE_AUDIO=1 SPACE_ASSETS_PATH=$(pwd)/assets ./build/space -m tools.torrent-download:main -- <magnet|info-hash|torrent-path>
Persistence:
~/.local/share/space/libtorrent/<info-hash>/(or$XDG_DATA_HOME/space/libtorrent/<info-hash>/)- includes torrent state metadata and downloaded data path
tools.torrent-upload
Purpose:
- publish a file/directory as a torrent
- print magnet URI + info-hash
- seed until interrupted
- reuse previous publish metadata when source is unchanged
Run:
SPACE_DISABLE_AUDIO=1 SPACE_ASSETS_PATH=$(pwd)/assets ./build/space -m tools.torrent-upload:main -- <source-path>
Persistence:
~/.local/share/space/libtorrent/uploads/<source-id>/(or$XDG_DATA_HOME/space/libtorrent/uploads/<source-id>/)- contains
published.torrentandstate.json
Current source-change detection:
- metadata-based fingerprint over path/type/size/mtime/permissions
- this avoids full content re-hash on every start, but can miss edge cases where file bytes change without metadata change
bencode container wrappers
Lua tables are ambiguous between list and dictionary semantics for edge cases (especially empty containers). The module exposes explicit wrappers for unambiguous bencoding:
libtorrent.list(table)-> force list encoding (supports empty list)libtorrent.dict(table)-> force dictionary encoding
Use these wrappers when a protocol payload requires exact container shape.
source of truth
For exact function names and behavior, use:
src/lua_libtorrent.cppassets/lua/tests/test-libtorrent-offline.fnlassets/lua/tests/test-libtorrent-online.fnl
