Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,13 +150,14 @@ These are load-bearing and enforced in the code:

## API at a glance

69 public `bt*` handlers (full signatures in **[api-reference](docs/api-reference.md)**):
75 public `bt*` handlers (full signatures in **[api-reference](docs/api-reference.md)**):

| Group | Handlers |
|---|---|
| Session | `btStartSession` · `btStopSession` · `btLastError` · `btClearError` · `btSessionPause` · `btSessionResume` · `btSessionIsPaused` · `btListenPort` · `btFindTorrent` · `btDhtAnnounce` |
| Settings | `btSetInt` · `btSetBool` · `btSetString` · `btGetSetting` · `btSetEncryption` |
| Add / remove | `btAddMagnet` · `btAddTorrentFile` · `btAddTorrentWithResume` · `btRemoveTorrent` |
| Add / remove | `btAddMagnet` · `btAddTorrentFile` · `btAddTorrentWithResume` · `btRemoveTorrent` · `btAddMagnetEx` · `btAddTorrentFileEx` |
| Filter / streaming | `btIpFilterAdd` · `btIpFilterClear` · `btSetPieceDeadline` · `btClearPieceDeadlines` |
| Control | `btPause` · `btResume` · `btForceRecheck` · `btForceReannounce` · `btScrapeTracker` · `btClearTorrentError` |
| Priorities / limits | `btSetFilePriority` · `btSetFilePriorities` · `btSetPiecePriority` · `btSetTorrentLimits` · `btSetMaxConnections` · `btSetMaxUploads` |
| Flags / modes | `btSetTorrentFlags` · `btUnsetTorrentFlags` · `btSetSequentialDownload` · `btSetAutoManaged` · `btSetSuperSeeding` · `btSetShareMode` · `btSetUploadMode` |
Expand Down
33 changes: 33 additions & 0 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,31 @@ for announcing your own content's info-hash so others can find you swarm-side.

---

## Filtering & streaming

### `btIpFilterAdd(in pSession as Integer, in pStartIp as String, in pEndIp as String, in pBlock as Boolean) returns Integer`
Add an **inclusive IP range** to the session's IP filter; `pBlock = true` blocks
it (the default-empty filter allows everything). Rules accumulate. `pStartIp` and
`pEndIp` are textual addresses of the **same family** (both IPv4 or both IPv6),
e.g. `"1.2.3.0"` .. `"1.2.3.255"`. A blocked peer is dropped before connecting.
- **Usage:** command - `btIpFilterAdd sSession, "1.2.3.0", "1.2.3.255", true`.

### `btIpFilterClear(in pSession as Integer) returns Integer`
Remove all IP-filter rules (back to "allow everything").
- **Usage:** command - `btIpFilterClear sSession`.

### `btSetPieceDeadline(in pTorrent as Integer, in pPieceIndex as Integer, in pDeadlineMs as Integer) returns Integer`
**Streaming**: ask libtorrent to fetch a piece within `pDeadlineMs` milliseconds
from now, reordering requests so the soonest deadlines come first. Set deadlines
across the pieces you are about to play to stream in order / seek smoothly.
- **Usage:** command - `btSetPieceDeadline tH, 0, 5000`.

### `btClearPieceDeadlines(in pTorrent as Integer) returns Integer`
Remove all piece deadlines on the torrent.
- **Usage:** command - `btClearPieceDeadlines tH`.

---

## Add / remove torrents

### `btAddMagnet(in pSession as Integer, in pURI as String, in pSavePath as String) returns Integer`
Expand Down Expand Up @@ -177,6 +202,14 @@ Remove a torrent from the session. If `pDeleteFiles` is `true`, the downloaded
files are deleted too. Returns `0` / negative.
- **Usage:** command - `btRemoveTorrent sSession, tH, false`.

### `btAddMagnetEx(in pSession as Integer, in pURI as String, in pSavePath as String, in pFlags as String, in pMask as String) returns Integer` · `btAddTorrentFileEx(in pSession as Integer, in pData as Data, in pSavePath as String, in pFlags as String, in pMask as String) returns Integer`
Like `btAddMagnet` / `btAddTorrentFile`, but apply **add-time `torrent_flags`** —
set the bits named in `pFlags`, touching only the bits named in `pMask` (decimal
strings; combine the `kFlag*` constants). The classic use is **adding paused**
(`kFlagPaused`) so you can set file priorities *before* it starts downloading, or
`kFlagSequentialDownload` for immediate streaming. Returns the torrent handle.
- **Usage:** function - `put btAddMagnetEx(sSession, tURI, tPath, kFlagPaused, kFlagPaused) into tH` (added paused), then set priorities, then `btResume tH`.

---

## Control
Expand Down
33 changes: 32 additions & 1 deletion src/btx_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ extern "C" {
* signature, a new record fieldId or alert code, or a framing change. The LCB
* layer hard-codes the matching number in checkABI() and refuses to run on
* skew. Start at 1. */
#define BTX_ABI_VERSION 7
#define BTX_ABI_VERSION 8

/* ----------------------------------------------------------- export linkage */

Expand Down Expand Up @@ -151,6 +151,24 @@ BTX_API int BTX_CALL btx_find_torrent(int s, const char *infoHashHex);
* this 40-hex info-hash on `port` (0 == our listen port). Fire-and-forget. */
BTX_API int BTX_CALL btx_dht_announce(int s, const char *infoHashHex, int port);

/* ====================================================================== *
* Filtering & streaming (ABI v8)
* ====================================================================== */

/* Add an inclusive IP range to the session IP filter; block != 0 blocks it
* (the default-empty filter allows everything). Rules accumulate (read-modify-
* write). `startIp`/`endIp` are textual addresses of the SAME family (both IPv4
* or both IPv6), e.g. "1.2.3.0".."1.2.3.255". btx_ip_filter_clear removes all. */
BTX_API int BTX_CALL btx_ip_filter_add(int s, const char *startIp,
const char *endIp, int block);
BTX_API int BTX_CALL btx_ip_filter_clear(int s);

/* Streaming: ask libtorrent to fetch a piece by a deadline (milliseconds from
* now), reordering requests so the soonest deadlines come first. Clear removes
* all deadlines. The data still rides engine ⇄ disk; only the hint crosses. */
BTX_API int BTX_CALL btx_set_piece_deadline(int t, int pieceIndex, int deadlineMs);
BTX_API int BTX_CALL btx_clear_piece_deadlines(int t);

/* ====================================================================== *
* Add / remove torrents
* ====================================================================== */
Expand All @@ -170,6 +188,19 @@ BTX_API int BTX_CALL btx_add_torrent_file(int s, const void *data, int len,
BTX_API int BTX_CALL btx_add_with_resume(int s, const void *resume, int len,
const char *savePath);

/* Extended add (ABI v8): like btx_add_magnet / btx_add_torrent_file but apply
* add-time torrent_flags — set the bits named in `maskDec` to `flagsDec`,
* leaving the rest at libtorrent's default. The common use is adding PAUSED
* (kFlagPaused) so you can set file priorities before it starts, or
* kFlagSequentialDownload for immediate streaming. */
BTX_API int BTX_CALL btx_add_magnet_ex(int s, const char *uri,
const char *savePath,
const char *flagsDec, const char *maskDec);
BTX_API int BTX_CALL btx_add_torrent_file_ex(int s, const void *data, int len,
const char *savePath,
const char *flagsDec,
const char *maskDec);

/* Remove a torrent; deleteFiles != 0 also deletes downloaded files. */
BTX_API int BTX_CALL btx_remove(int s, int t, int deleteFiles);

Expand Down
78 changes: 77 additions & 1 deletion src/torrent.lcb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ metadata title is "TorrentXT"
-- ===================================================================== --

-- Must equal BTX_ABI_VERSION in src/btx_abi.h; _checkABI() throws on skew.
constant kABIVersion is 7
constant kABIVersion is 8

-- libfoundation MCStringEncoding value for UTF-8 (ASCII=0, Windows1252=1,
-- MacRoman=2, ISO8859_1=3, UTF8=4). Used by _decodeText via MCStringDecode.
Expand Down Expand Up @@ -223,6 +223,14 @@ private foreign handler _btx_dht_announce(in pS as CInt, in pHash as ZStringUTF8

private foreign handler _btx_add_magnet(in pS as CInt, in pURI as ZStringUTF8, in pSave as ZStringUTF8) returns CInt binds to "c:torrentxt>btx_add_magnet!cdecl"
private foreign handler _btx_add_torrent_file(in pS as CInt, in pData as Pointer, in pLen as CInt, in pSave as ZStringUTF8) returns CInt binds to "c:torrentxt>btx_add_torrent_file!cdecl"

-- extended add + filtering/streaming (ABI v8)
private foreign handler _btx_add_magnet_ex(in pS as CInt, in pURI as ZStringUTF8, in pSave as ZStringUTF8, in pFlags as ZStringUTF8, in pMask as ZStringUTF8) returns CInt binds to "c:torrentxt>btx_add_magnet_ex!cdecl"
private foreign handler _btx_add_torrent_file_ex(in pS as CInt, in pData as Pointer, in pLen as CInt, in pSave as ZStringUTF8, in pFlags as ZStringUTF8, in pMask as ZStringUTF8) returns CInt binds to "c:torrentxt>btx_add_torrent_file_ex!cdecl"
private foreign handler _btx_ip_filter_add(in pS as CInt, in pStart as ZStringUTF8, in pEnd as ZStringUTF8, in pBlock as CInt) returns CInt binds to "c:torrentxt>btx_ip_filter_add!cdecl"
private foreign handler _btx_ip_filter_clear(in pS as CInt) returns CInt binds to "c:torrentxt>btx_ip_filter_clear!cdecl"
private foreign handler _btx_set_piece_deadline(in pT as CInt, in pPiece as CInt, in pDeadline as CInt) returns CInt binds to "c:torrentxt>btx_set_piece_deadline!cdecl"
private foreign handler _btx_clear_piece_deadlines(in pT as CInt) returns CInt binds to "c:torrentxt>btx_clear_piece_deadlines!cdecl"
private foreign handler _btx_add_with_resume(in pS as CInt, in pData as Pointer, in pLen as CInt, in pSave as ZStringUTF8) returns CInt binds to "c:torrentxt>btx_add_with_resume!cdecl"
private foreign handler _btx_remove(in pS as CInt, in pT as CInt, in pDel as CInt) returns CInt binds to "c:torrentxt>btx_remove!cdecl"

Expand Down Expand Up @@ -872,6 +880,74 @@ public handler btAddTorrentFile(in pSession as Integer, in pData as Data, in pSa
return tHandle
end handler

-- ---- extended add: add with add-time torrent_flags (ABI v8) --------------
-- pFlags / pMask are decimal strings (combine kFlag* constants). The common
-- use is adding PAUSED (kFlagPaused) to set file priorities before it starts.

public handler btAddMagnetEx(in pSession as Integer, in pURI as String, in pSavePath as String, in pFlags as String, in pMask as String) returns Integer
variable tHandle as Integer
unsafe
put _btx_add_magnet_ex(pSession, pURI, pSavePath, pFlags, pMask) into tHandle
end unsafe
return tHandle
end handler

public handler btAddTorrentFileEx(in pSession as Integer, in pData as Data, in pSavePath as String, in pFlags as String, in pMask as String) returns Integer
variable tHandle as Integer
variable tPtr as Pointer
put 0 into tHandle
if the number of bytes in pData > 0 then
unsafe
put MCDataGetBytePtr(pData) into tPtr
put _btx_add_torrent_file_ex(pSession, tPtr, the number of bytes in pData, pSavePath, pFlags, pMask) into tHandle
end unsafe
end if
return tHandle
end handler

-- ---- IP filter + streaming (ABI v8) --------------------------------------

-- Add an inclusive IP range to the session filter; pBlock true blocks it.
-- Rules accumulate. Start/end must be the same family (both v4 or both v6).
public handler btIpFilterAdd(in pSession as Integer, in pStartIp as String, in pEndIp as String, in pBlock as Boolean) returns Integer
variable tR as Integer
variable tB as Integer
if pBlock then
put 1 into tB
else
put 0 into tB
end if
unsafe
put _btx_ip_filter_add(pSession, pStartIp, pEndIp, tB) into tR
end unsafe
return tR
end handler

public handler btIpFilterClear(in pSession as Integer) returns Integer
variable tR as Integer
unsafe
put _btx_ip_filter_clear(pSession) into tR
end unsafe
return tR
end handler

-- Streaming: fetch a piece by a deadline (ms from now); soonest deadlines win.
public handler btSetPieceDeadline(in pTorrent as Integer, in pPieceIndex as Integer, in pDeadlineMs as Integer) returns Integer
variable tR as Integer
unsafe
put _btx_set_piece_deadline(pTorrent, pPieceIndex, pDeadlineMs) into tR
end unsafe
return tR
end handler

public handler btClearPieceDeadlines(in pTorrent as Integer) returns Integer
variable tR as Integer
unsafe
put _btx_clear_piece_deadlines(pTorrent) into tR
end unsafe
return tR
end handler

public handler btAddTorrentWithResume(in pSession as Integer, in pResume as Data, in pSavePath as String) returns Integer
variable tHandle as Integer
variable tPtr as Pointer
Expand Down
118 changes: 118 additions & 0 deletions src/torrent_shim.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@
#include <libtorrent/read_resume_data.hpp>
#include <libtorrent/info_hash.hpp>
#include <libtorrent/sha1_hash.hpp>
#include <libtorrent/ip_filter.hpp> /* set_ip_filter (ABI v8) */
#include <libtorrent/address.hpp> /* make_address for IP-filter rules */
#include <libtorrent/download_priority.hpp>
#include <libtorrent/units.hpp>
#include <libtorrent/create_torrent.hpp>
Expand Down Expand Up @@ -695,12 +697,85 @@ extern "C" BTX_API int BTX_CALL btx_dht_announce(int s, const char *infoHashHex,
});
}

/* ====================================================================== *
* Filtering & streaming (ABI v8) — IP filter rules and piece deadlines.
* ====================================================================== */

extern "C" BTX_API int BTX_CALL btx_ip_filter_add(int s, const char *startIp,
const char *endIp, int block) {
BTX_GUARD_ACTION({
SessionState *st = session_for(s);
if (!st || !st->ses) { set_error("no live session"); return BTX_ERR_NO_SESSION; }
if (!startIp || !endIp) { set_error("null ip range"); return BTX_ERR_INVALID_ARG; }
lt::error_code ec;
lt::address first = lt::make_address(startIp, ec);
if (ec) { set_error("bad start address"); return BTX_ERR_INVALID_ARG; }
lt::address last = lt::make_address(endIp, ec);
if (ec) { set_error("bad end address"); return BTX_ERR_INVALID_ARG; }
if (first.is_v4() != last.is_v4()) {
set_error("ip range start/end must be the same family");
return BTX_ERR_INVALID_ARG;
}
/* read-modify-write the session filter so rules accumulate. */
lt::ip_filter f = st->ses->get_ip_filter();
std::uint32_t flags = block
? static_cast<std::uint32_t>(lt::ip_filter::blocked) : 0u;
f.add_rule(first, last, flags);
st->ses->set_ip_filter(f);
return BTX_OK;
});
}

extern "C" BTX_API int BTX_CALL btx_ip_filter_clear(int s) {
BTX_GUARD_ACTION({
SessionState *st = session_for(s);
if (!st || !st->ses) { set_error("no live session"); return BTX_ERR_NO_SESSION; }
/* an empty filter allows everything. */
st->ses->set_ip_filter(lt::ip_filter());
return BTX_OK;
});
}

extern "C" BTX_API int BTX_CALL btx_set_piece_deadline(int t, int pieceIndex,
int deadlineMs) {
BTX_GUARD_ACTION({
bool ok = false; lt::torrent_handle h = torrent_only(t, nullptr, &ok);
if (!ok) { set_error("bad torrent handle"); return BTX_ERR_BAD_HANDLE; }
if (pieceIndex < 0) { set_error("negative piece index"); return BTX_ERR_INVALID_ARG; }
/* deadline is milliseconds from now; libtorrent reorders requests to hit
* the soonest deadlines first (streaming / seeking). */
h.set_piece_deadline(lt::piece_index_t{pieceIndex}, deadlineMs);
return BTX_OK;
});
}

extern "C" BTX_API int BTX_CALL btx_clear_piece_deadlines(int t) {
BTX_GUARD_ACTION({
bool ok = false; lt::torrent_handle h = torrent_only(t, nullptr, &ok);
if (!ok) { set_error("bad torrent handle"); return BTX_ERR_BAD_HANDLE; }
h.clear_piece_deadlines();
return BTX_OK;
});
}

/* ====================================================================== *
* Add / remove torrents
* ====================================================================== */

namespace {

/* Apply add-time torrent_flags from two decimal strings: set the bits named in
* `maskDec` to the values in `flagsDec`, leaving everything else at libtorrent's
* default. Used by the _ex add variants (e.g. add PAUSED, or sequential). A null
* pair is a no-op (the plain add path). */
void apply_add_flags(lt::add_torrent_params &atp, const char *flagsDec,
const char *maskDec) {
if (!flagsDec || !maskDec) return;
lt::torrent_flags_t fl{std::strtoull(flagsDec, nullptr, 10)};
lt::torrent_flags_t mk{std::strtoull(maskDec, nullptr, 10)};
atp.flags = (atp.flags & ~mk) | (fl & mk);
}

/* Register a freshly-added handle in the torrent table + reverse map and return
* our int id (0 if the table is full). Centralised so add_magnet / add_file /
* add_resume all map identically. */
Expand Down Expand Up @@ -791,6 +866,49 @@ extern "C" BTX_API int BTX_CALL btx_add_with_resume(int s, const void *resume,
});
}

/* Extended add (ABI v8): the plain add path + add-time torrent_flags. */
extern "C" BTX_API int BTX_CALL btx_add_magnet_ex(int s, const char *uri,
const char *savePath,
const char *flagsDec,
const char *maskDec) {
BTX_GUARD_INT({
SessionState *st = session_for(s);
if (!st || !st->ses) { set_error("no live session"); return 0; }
if (!uri || !*uri) { set_error("empty magnet URI"); return 0; }
lt::error_code ec;
lt::add_torrent_params atp = lt::parse_magnet_uri(uri, ec);
if (ec) { set_error("bad magnet URI: " + ec.message()); return 0; }
if (savePath && *savePath) atp.save_path = savePath;
apply_add_flags(atp, flagsDec, maskDec);
lt::torrent_handle h = st->ses->add_torrent(std::move(atp), ec);
if (ec) { set_error("add_torrent failed: " + ec.message()); return 0; }
return register_torrent(st, h);
});
}

extern "C" BTX_API int BTX_CALL btx_add_torrent_file_ex(int s, const void *data,
int len,
const char *savePath,
const char *flagsDec,
const char *maskDec) {
BTX_GUARD_INT({
SessionState *st = session_for(s);
if (!st || !st->ses) { set_error("no live session"); return 0; }
if (!data || len <= 0) { set_error("empty .torrent buffer"); return 0; }
lt::error_code ec;
auto ti = std::make_shared<lt::torrent_info>(
reinterpret_cast<char const *>(data), len, ec);
if (ec) { set_error("invalid .torrent: " + ec.message()); return 0; }
lt::add_torrent_params atp;
atp.ti = ti;
if (savePath && *savePath) atp.save_path = savePath;
apply_add_flags(atp, flagsDec, maskDec);
lt::torrent_handle h = st->ses->add_torrent(std::move(atp), ec);
if (ec) { set_error("add_torrent failed: " + ec.message()); return 0; }
return register_torrent(st, h);
});
}

extern "C" BTX_API int BTX_CALL btx_remove(int s, int t, int deleteFiles) {
BTX_GUARD_ACTION({
bool ok = false;
Expand Down
10 changes: 10 additions & 0 deletions tests/torrent_smoke_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,16 @@ static void test_bogus_handles_are_noops() {
CHECK(btx_dht_announce(h,
"0123456789abcdef0123456789abcdef01234567", 6881) < 0);

/* --- ABI v8 filtering / streaming / extended add on a bogus handle --- */
CHECK(btx_ip_filter_add(h, "1.2.3.0", "1.2.3.255", 1) < 0);
CHECK(btx_ip_filter_clear(h) < 0);
CHECK(btx_set_piece_deadline(h, 0, 5000) < 0);
CHECK(btx_clear_piece_deadlines(h) < 0);
CHECK(btx_add_magnet_ex(h, "magnet:?xt=urn:btih:"
"0123456789abcdef0123456789abcdef01234567", "/tmp", "16", "16") == 0);
const unsigned char d8[4] = {'d', '3', ':', 'e'};
CHECK(btx_add_torrent_file_ex(h, d8, sizeof d8, "/tmp", "16", "16") == 0);

/* add-* on a bogus session return 0 (no handle made), not a crash. */
CHECK(btx_add_magnet(h, "magnet:?xt=urn:btih:"
"0123456789abcdef0123456789abcdef01234567", "/tmp") == 0);
Expand Down
Loading