librats 2.3.2 → 2.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +1 -0
- package/native-src/src/librats/bindings/rats.cpp +3 -2
- package/native-src/src/librats/bindings/rats.h +2 -1
- package/native-src/src/librats/subsystems/file_transfer.cpp +54 -20
- package/native-src/src/librats/subsystems/file_transfer.h +21 -3
- package/package.json +1 -1
- package/src/librats_node.cpp +6 -3
package/lib/index.d.ts
CHANGED
|
@@ -574,8 +574,9 @@ rats_error_t rats_on_file_complete(rats_t node, rats_file_complete_cb cb, void*
|
|
|
574
574
|
if (!cb) return RATS_ERR_INVALID_ARG;
|
|
575
575
|
auto* h = as_handle(node);
|
|
576
576
|
if (!h->files) return RATS_ERR_NOT_ENABLED;
|
|
577
|
-
h->files->on_complete([cb, user](uint64_t id, bool success, const std::string& path) {
|
|
578
|
-
|
|
577
|
+
h->files->on_complete([cb, user](const PeerId& peer, uint64_t id, bool success, const std::string& path) {
|
|
578
|
+
const std::string hex = peer.to_hex();
|
|
579
|
+
cb(user, id, hex.c_str(), success ? 1 : 0, path.c_str());
|
|
579
580
|
});
|
|
580
581
|
return RATS_OK;
|
|
581
582
|
}
|
|
@@ -345,7 +345,8 @@ typedef void (*rats_file_offer_cb)(void* user, const char* peer_id_hex, uint64_t
|
|
|
345
345
|
const char* name, uint64_t size, int is_directory);
|
|
346
346
|
typedef void (*rats_file_progress_cb)(void* user, uint64_t transfer_id, const char* peer_id_hex,
|
|
347
347
|
uint64_t bytes_transferred, uint64_t total_bytes, int status);
|
|
348
|
-
typedef void (*rats_file_complete_cb)(void* user, uint64_t transfer_id,
|
|
348
|
+
typedef void (*rats_file_complete_cb)(void* user, uint64_t transfer_id, const char* peer_id_hex,
|
|
349
|
+
int success, const char* path);
|
|
349
350
|
|
|
350
351
|
/** Enable the file-transfer subsystem. `temp_dir` holds in-progress downloads
|
|
351
352
|
* (NULL → current directory). Call before start(). */
|
|
@@ -143,12 +143,23 @@ FileTransfer::Stats FileTransfer::stats() const {
|
|
|
143
143
|
|
|
144
144
|
// ── lookups ───────────────────────────────────────────────────────────────────
|
|
145
145
|
|
|
146
|
-
std::shared_ptr<FileTransfer::Outgoing> FileTransfer::
|
|
146
|
+
std::shared_ptr<FileTransfer::Outgoing> FileTransfer::find_own_outgoing(uint64_t id) const {
|
|
147
147
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
148
148
|
auto it = outgoing_.find(id);
|
|
149
149
|
return it == outgoing_.end() ? nullptr : it->second;
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
std::shared_ptr<FileTransfer::Outgoing> FileTransfer::find_outgoing(const PeerId& peer, uint64_t id) const {
|
|
153
|
+
std::lock_guard<std::mutex> lock(mutex_);
|
|
154
|
+
auto it = outgoing_.find(id);
|
|
155
|
+
if (it == outgoing_.end()) return nullptr;
|
|
156
|
+
// The id alone found it; the peer is what proves the sender is entitled to it.
|
|
157
|
+
// Outgoing ids come from one node-wide counter, so a peer that never received
|
|
158
|
+
// this id can still name it — and every control opcode below would otherwise
|
|
159
|
+
// act on a stranger's transfer.
|
|
160
|
+
return it->second->peer == peer ? it->second : nullptr;
|
|
161
|
+
}
|
|
162
|
+
|
|
152
163
|
std::shared_ptr<FileTransfer::Incoming> FileTransfer::find_incoming(const PeerId& peer, uint64_t id) const {
|
|
153
164
|
std::lock_guard<std::mutex> lock(mutex_);
|
|
154
165
|
auto pit = incoming_.find(peer);
|
|
@@ -241,7 +252,7 @@ void FileTransfer::worker_loop() {
|
|
|
241
252
|
id = send_queue_.front();
|
|
242
253
|
send_queue_.pop();
|
|
243
254
|
}
|
|
244
|
-
auto t =
|
|
255
|
+
auto t = find_own_outgoing(id); // id came from our queue, not from a peer
|
|
245
256
|
if (!t) continue;
|
|
246
257
|
{
|
|
247
258
|
std::lock_guard<std::mutex> lk(t->mtx);
|
|
@@ -380,7 +391,7 @@ void FileTransfer::finish_outgoing(const std::shared_ptr<Outgoing>& t, bool succ
|
|
|
380
391
|
if (cancelled) LOG_INFO("filexfer", "Send [" << t->id << "] cancelled");
|
|
381
392
|
else if (success) LOG_INFO("filexfer", "Send [" << t->id << "] completed (" << t->root << ")");
|
|
382
393
|
else LOG_WARN("filexfer", "Send [" << t->id << "] failed");
|
|
383
|
-
if (complete_handler_) complete_handler_(t->id, success, t->root);
|
|
394
|
+
if (complete_handler_) complete_handler_(t->peer, t->id, success, t->root);
|
|
384
395
|
}
|
|
385
396
|
|
|
386
397
|
// ── Receiver ──────────────────────────────────────────────────────────────────
|
|
@@ -428,7 +439,7 @@ void FileTransfer::reject(const PeerId& from, uint64_t id) {
|
|
|
428
439
|
if (pit != incoming_.end()) pit->second.erase(id);
|
|
429
440
|
}
|
|
430
441
|
Bytes m; m.push_back(OP_RESPONSE); put_u64(m, id); m.push_back(0); send_to(from, m);
|
|
431
|
-
if (t && complete_handler_) complete_handler_(id, false, "");
|
|
442
|
+
if (t && complete_handler_) complete_handler_(from, id, false, "");
|
|
432
443
|
}
|
|
433
444
|
|
|
434
445
|
// ── Receive-side disk writer (off the reactor thread) ─────────────────────────
|
|
@@ -627,7 +638,7 @@ void FileTransfer::finish_incoming(const std::shared_ptr<Incoming>& t, bool succ
|
|
|
627
638
|
else if (success) LOG_INFO("filexfer", "Receive [" << t->id << "] completed -> " << dest);
|
|
628
639
|
else if (error.empty()) LOG_WARN("filexfer", "Receive [" << t->id << "] failed");
|
|
629
640
|
else LOG_WARN("filexfer", "Receive [" << t->id << "] failed: " << error);
|
|
630
|
-
if (complete_handler_) complete_handler_(t->id, success, dest);
|
|
641
|
+
if (complete_handler_) complete_handler_(t->peer, t->id, success, dest);
|
|
631
642
|
}
|
|
632
643
|
|
|
633
644
|
// ── Message dispatch (reactor thread) ────────────────────────────────────────
|
|
@@ -675,7 +686,7 @@ void FileTransfer::on_message(const Peer& peer, ByteView payload) {
|
|
|
675
686
|
const uint64_t id = r.u64();
|
|
676
687
|
const uint8_t accepted = r.u8();
|
|
677
688
|
if (!r.ok) return;
|
|
678
|
-
auto t = find_outgoing(id);
|
|
689
|
+
auto t = find_outgoing(from, id);
|
|
679
690
|
if (!t) return;
|
|
680
691
|
if (!accepted) { finish_outgoing(t, false); return; }
|
|
681
692
|
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Pending) t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); }
|
|
@@ -703,7 +714,7 @@ void FileTransfer::on_message(const Peer& peer, ByteView payload) {
|
|
|
703
714
|
const uint64_t id = r.u64();
|
|
704
715
|
const uint64_t received = r.u64();
|
|
705
716
|
if (!r.ok) return;
|
|
706
|
-
auto t = find_outgoing(id);
|
|
717
|
+
auto t = find_outgoing(from, id);
|
|
707
718
|
if (!t) return;
|
|
708
719
|
{ std::lock_guard<std::mutex> lk(t->mtx); t->acked = received; t->last_activity = std::chrono::steady_clock::now(); }
|
|
709
720
|
t->cv.notify_all();
|
|
@@ -713,13 +724,13 @@ void FileTransfer::on_message(const Peer& peer, ByteView payload) {
|
|
|
713
724
|
const uint64_t id = r.u64();
|
|
714
725
|
const uint8_t ok = r.u8();
|
|
715
726
|
if (!r.ok) return;
|
|
716
|
-
if (auto t = find_outgoing(id)) finish_outgoing(t, ok != 0);
|
|
727
|
+
if (auto t = find_outgoing(from, id)) finish_outgoing(t, ok != 0);
|
|
717
728
|
return;
|
|
718
729
|
}
|
|
719
730
|
case OP_CANCEL: {
|
|
720
731
|
const uint64_t id = r.u64();
|
|
721
732
|
if (!r.ok) return;
|
|
722
|
-
if (auto t = find_outgoing(id)) {
|
|
733
|
+
if (auto t = find_outgoing(from, id)) {
|
|
723
734
|
{ std::lock_guard<std::mutex> lk(t->mtx); t->status = Status::Cancelled; t->cv.notify_all(); }
|
|
724
735
|
finish_outgoing(t, false);
|
|
725
736
|
}
|
|
@@ -732,14 +743,14 @@ void FileTransfer::on_message(const Peer& peer, ByteView payload) {
|
|
|
732
743
|
case OP_PAUSE: {
|
|
733
744
|
const uint64_t id = r.u64();
|
|
734
745
|
if (!r.ok) return;
|
|
735
|
-
if (auto t = find_outgoing(id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; t->cv.notify_all(); }
|
|
746
|
+
if (auto t = find_outgoing(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; t->cv.notify_all(); }
|
|
736
747
|
if (auto t = find_incoming(from, id)) { std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) t->status = Status::Paused; }
|
|
737
748
|
return;
|
|
738
749
|
}
|
|
739
750
|
case OP_RESUME: {
|
|
740
751
|
const uint64_t id = r.u64();
|
|
741
752
|
if (!r.ok) return;
|
|
742
|
-
if (auto t = find_outgoing(id)) {
|
|
753
|
+
if (auto t = find_outgoing(from, id)) {
|
|
743
754
|
bool requeue = false;
|
|
744
755
|
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); requeue = true; } t->cv.notify_all(); }
|
|
745
756
|
if (requeue) queue_send(id);
|
|
@@ -860,7 +871,7 @@ void FileTransfer::handle_file_end(const PeerId& from, uint64_t id, uint32_t fid
|
|
|
860
871
|
|
|
861
872
|
bool FileTransfer::cancel(const PeerId& peer, uint64_t id) {
|
|
862
873
|
bool acted = false;
|
|
863
|
-
if (auto t = find_outgoing(id)) {
|
|
874
|
+
if (auto t = find_outgoing(peer, id)) {
|
|
864
875
|
{ std::lock_guard<std::mutex> lk(t->mtx); if (!t->finished) { t->status = Status::Cancelled; t->cv.notify_all(); acted = true; } }
|
|
865
876
|
if (acted) { send_simple(t->peer, OP_CANCEL, id); finish_outgoing(t, false); }
|
|
866
877
|
}
|
|
@@ -873,7 +884,7 @@ bool FileTransfer::cancel(const PeerId& peer, uint64_t id) {
|
|
|
873
884
|
}
|
|
874
885
|
|
|
875
886
|
bool FileTransfer::pause(const PeerId& peer, uint64_t id) {
|
|
876
|
-
if (auto t = find_outgoing(id)) {
|
|
887
|
+
if (auto t = find_outgoing(peer, id)) {
|
|
877
888
|
bool ok = false;
|
|
878
889
|
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Active) { t->status = Status::Paused; ok = true; t->cv.notify_all(); } }
|
|
879
890
|
if (ok) { send_simple(t->peer, OP_PAUSE, id); return true; }
|
|
@@ -887,7 +898,7 @@ bool FileTransfer::pause(const PeerId& peer, uint64_t id) {
|
|
|
887
898
|
}
|
|
888
899
|
|
|
889
900
|
bool FileTransfer::resume(const PeerId& peer, uint64_t id) {
|
|
890
|
-
if (auto t = find_outgoing(id)) {
|
|
901
|
+
if (auto t = find_outgoing(peer, id)) {
|
|
891
902
|
bool ok = false;
|
|
892
903
|
{ std::lock_guard<std::mutex> lk(t->mtx); if (t->status == Status::Paused) { t->status = Status::Active; t->last_activity = std::chrono::steady_clock::now(); ok = true; t->cv.notify_all(); } }
|
|
893
904
|
if (ok) { send_simple(t->peer, OP_RESUME, id); queue_send(id); return true; }
|
|
@@ -940,7 +951,16 @@ void FileTransfer::maintenance_loop() {
|
|
|
940
951
|
if (!running_.load()) return;
|
|
941
952
|
|
|
942
953
|
const auto now = std::chrono::steady_clock::now();
|
|
943
|
-
|
|
954
|
+
|
|
955
|
+
// Two deadlines, because Pending and Active are waiting on different
|
|
956
|
+
// things. An Active transfer is waiting on the wire, so silence means the
|
|
957
|
+
// link is gone. A Pending one is waiting on the far side's application to
|
|
958
|
+
// answer the offer, which legitimately takes longer than a network
|
|
959
|
+
// round-trip — the app may be prompting a user or sitting behind a busy
|
|
960
|
+
// event loop. Sharing one deadline meant an offer nobody had answered yet
|
|
961
|
+
// was killed as if the peer had vanished.
|
|
962
|
+
const uint32_t idle_timeout = config_.transfer_timeout_secs;
|
|
963
|
+
const uint32_t offer_timeout = config_.offer_timeout_secs;
|
|
944
964
|
|
|
945
965
|
std::vector<std::shared_ptr<Outgoing>> out;
|
|
946
966
|
std::vector<std::shared_ptr<Incoming>> in;
|
|
@@ -950,21 +970,35 @@ void FileTransfer::maintenance_loop() {
|
|
|
950
970
|
for (auto& [peer, m] : incoming_) for (auto& [id, t] : m) in.push_back(t);
|
|
951
971
|
}
|
|
952
972
|
|
|
973
|
+
auto expired = [&](Status status, std::chrono::steady_clock::time_point since) {
|
|
974
|
+
const uint32_t limit = (status == Status::Pending) ? offer_timeout : idle_timeout;
|
|
975
|
+
return limit > 0
|
|
976
|
+
&& std::chrono::duration_cast<std::chrono::seconds>(now - since).count() > limit;
|
|
977
|
+
};
|
|
978
|
+
|
|
953
979
|
for (auto& t : out) {
|
|
954
980
|
bool stale = false;
|
|
955
981
|
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
956
|
-
if (!t->finished && t->status != Status::Paused &&
|
|
957
|
-
std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
|
|
982
|
+
if (!t->finished && t->status != Status::Paused && expired(t->status, t->last_activity))
|
|
958
983
|
stale = true; }
|
|
959
984
|
if (stale) { LOG_WARN("filexfer", "outgoing transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_outgoing(t, false); }
|
|
960
985
|
}
|
|
961
986
|
for (auto& t : in) {
|
|
962
987
|
bool stale = false;
|
|
963
988
|
{ std::lock_guard<std::mutex> lk(t->mtx);
|
|
964
|
-
if (!t->finished && t->status != Status::Paused && t->status
|
|
965
|
-
std::chrono::duration_cast<std::chrono::seconds>(now - t->last_activity).count() > timeout)
|
|
989
|
+
if (!t->finished && t->status != Status::Paused && expired(t->status, t->last_activity))
|
|
966
990
|
stale = true; }
|
|
967
|
-
|
|
991
|
+
// A Pending incoming transfer is an offer our own app never answered.
|
|
992
|
+
// It used to be exempt from every deadline and so leaked for the life
|
|
993
|
+
// of the process; reclaiming it is a reject, not a network failure.
|
|
994
|
+
bool pending = false;
|
|
995
|
+
{ std::lock_guard<std::mutex> lk(t->mtx); pending = (t->status == Status::Pending); }
|
|
996
|
+
if (stale && pending) {
|
|
997
|
+
LOG_WARN("filexfer", "incoming offer " << t->id << " was never answered; rejecting");
|
|
998
|
+
reject(t->peer, t->id);
|
|
999
|
+
} else if (stale) {
|
|
1000
|
+
LOG_WARN("filexfer", "incoming transfer " << t->id << " timed out"); send_complete(t->peer, t->id, false); finish_incoming(t, false, "timed out");
|
|
1001
|
+
}
|
|
968
1002
|
}
|
|
969
1003
|
}
|
|
970
1004
|
}
|
|
@@ -77,7 +77,12 @@ public:
|
|
|
77
77
|
uint32_t chunk_size = 64 * 1024; ///< payload bytes per chunk
|
|
78
78
|
uint32_t window_bytes = 4 * 1024 * 1024; ///< max un-acked bytes in flight
|
|
79
79
|
uint32_t progress_interval = 256 * 1024; ///< receiver acks every N bytes
|
|
80
|
-
uint32_t transfer_timeout_secs = 60; ///< abort
|
|
80
|
+
uint32_t transfer_timeout_secs = 60; ///< abort an *active* transfer idle this long
|
|
81
|
+
// An offered transfer is waiting on a human or an app callback, not on the
|
|
82
|
+
// wire, so it gets its own (longer) deadline. Applied to both sides: the
|
|
83
|
+
// sender drops an offer nobody answered, and the receiver reclaims an offer
|
|
84
|
+
// its app never accepted or rejected.
|
|
85
|
+
uint32_t offer_timeout_secs = 300;
|
|
81
86
|
uint32_t worker_threads = 4; ///< concurrent outgoing transfers
|
|
82
87
|
uint32_t disk_threads = 4; ///< receive-side disk-writer pool size
|
|
83
88
|
bool verify_integrity = true; ///< whole-file SHA-256 end-to-end check
|
|
@@ -134,7 +139,12 @@ public:
|
|
|
134
139
|
|
|
135
140
|
using OfferHandler = std::function<void(const Offer&)>;
|
|
136
141
|
using ProgressHandler = std::function<void(const Progress&)>;
|
|
137
|
-
|
|
142
|
+
// The peer is part of the identity of a transfer, not decoration: incoming ids
|
|
143
|
+
// are allocated by the *sender*, so every node's first offer is id 1 and two
|
|
144
|
+
// peers routinely have a transfer of the same id in flight at once. A handler
|
|
145
|
+
// given only the id cannot tell which of them finished.
|
|
146
|
+
using CompleteHandler =
|
|
147
|
+
std::function<void(const PeerId& peer, uint64_t id, bool success, const std::string& path)>;
|
|
138
148
|
|
|
139
149
|
explicit FileTransfer(std::string temp_dir = ".");
|
|
140
150
|
explicit FileTransfer(Config config);
|
|
@@ -318,7 +328,15 @@ private:
|
|
|
318
328
|
void emit_progress(const std::shared_ptr<Outgoing>& t);
|
|
319
329
|
void emit_progress(const std::shared_ptr<Incoming>& t);
|
|
320
330
|
|
|
321
|
-
|
|
331
|
+
// Both lookups are peer-scoped. Outgoing ids are unique on this node, so the
|
|
332
|
+
// peer is not needed to *find* the transfer — it is needed to establish that
|
|
333
|
+
// the sender of the message is the transfer's counterparty. Without that check
|
|
334
|
+
// any connected peer can cancel, complete or stall somebody else's transfer by
|
|
335
|
+
// naming an id it was never given.
|
|
336
|
+
std::shared_ptr<Outgoing> find_outgoing(const PeerId& peer, uint64_t id) const;
|
|
337
|
+
// Unchecked lookup, for ids that came off our own send queue rather than off
|
|
338
|
+
// the wire. Never call it with an id a peer supplied.
|
|
339
|
+
std::shared_ptr<Outgoing> find_own_outgoing(uint64_t id) const;
|
|
322
340
|
std::shared_ptr<Incoming> find_incoming(const PeerId& peer, uint64_t id) const;
|
|
323
341
|
|
|
324
342
|
void send_to(const PeerId& peer, const Bytes& msg) {
|
package/package.json
CHANGED
package/src/librats_node.cpp
CHANGED
|
@@ -855,7 +855,7 @@ void RatsNode::OnFileProgress(const Napi::CallbackInfo& info) {
|
|
|
855
855
|
throw_on_error(env, rats_on_file_progress(node_, trampoline, on_file_progress_.get()));
|
|
856
856
|
}
|
|
857
857
|
|
|
858
|
-
// rats_file_complete_cb(user, transfer_id, success, path)
|
|
858
|
+
// rats_file_complete_cb(user, transfer_id, peer_id_hex, success, path)
|
|
859
859
|
void RatsNode::OnFileComplete(const Napi::CallbackInfo& info) {
|
|
860
860
|
RATS_REQUIRE_NODE();
|
|
861
861
|
Napi::Env env = info.Env();
|
|
@@ -865,12 +865,15 @@ void RatsNode::OnFileComplete(const Napi::CallbackInfo& info) {
|
|
|
865
865
|
}
|
|
866
866
|
on_file_complete_ = std::make_unique<CbContext>();
|
|
867
867
|
on_file_complete_->init(env, info[0].As<Napi::Function>(), "on_file_complete");
|
|
868
|
-
auto trampoline = [](void* user, uint64_t transfer_id,
|
|
868
|
+
auto trampoline = [](void* user, uint64_t transfer_id, const char* peer_id_hex, int success,
|
|
869
|
+
const char* path) {
|
|
869
870
|
auto* c = static_cast<CbContext*>(user);
|
|
870
871
|
std::string p = path ? path : "";
|
|
872
|
+
std::string peer = peer_id_hex ? peer_id_hex : "";
|
|
871
873
|
bool ok = success != 0;
|
|
872
|
-
c->tsfn.BlockingCall([transfer_id, ok, p](Napi::Env env, Napi::Function js) {
|
|
874
|
+
c->tsfn.BlockingCall([transfer_id, peer, ok, p](Napi::Env env, Napi::Function js) {
|
|
873
875
|
js.Call({Napi::Number::New(env, static_cast<double>(transfer_id)),
|
|
876
|
+
Napi::String::New(env, peer),
|
|
874
877
|
Napi::Boolean::New(env, ok),
|
|
875
878
|
Napi::String::New(env, p)});
|
|
876
879
|
});
|