appium-ios-tuntap 0.2.4 → 0.2.5
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/CHANGELOG.md +6 -0
- package/binding.gyp +5 -2
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/darwin-x64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-arm64/appium-ios-tuntap.node +0 -0
- package/prebuilds/linux-x64/appium-ios-tuntap.node +0 -0
- package/src/native/posix_tun_backend.h +58 -0
- package/src/native/posix_uv_poll_loop.cc +137 -0
- package/src/native/posix_uv_poll_loop.h +55 -0
- package/src/native/tun_backend.h +33 -10
- package/src/native/tun_backend_darwin.cc +32 -25
- package/src/native/tun_backend_linux.cc +35 -27
- package/src/tuntap.cc +75 -137
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [0.2.5](https://github.com/appium/appium-ios-tuntap/compare/v0.2.4...v0.2.5) (2026-05-14)
|
|
2
|
+
|
|
3
|
+
### Code Refactoring
|
|
4
|
+
|
|
5
|
+
* backends own fd, polling, and lifecycle ([#42](https://github.com/appium/appium-ios-tuntap/issues/42)) ([f089944](https://github.com/appium/appium-ios-tuntap/commit/f08994477cb9909b597386d0893abbf9b6a3c137))
|
|
6
|
+
|
|
1
7
|
## [0.2.4](https://github.com/appium/appium-ios-tuntap/compare/v0.2.3...v0.2.4) (2026-05-13)
|
|
2
8
|
|
|
3
9
|
### Code Refactoring
|
package/binding.gyp
CHANGED
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
{
|
|
4
4
|
"target_name": "tuntap",
|
|
5
5
|
"sources": [
|
|
6
|
-
"src/tuntap.cc"
|
|
7
|
-
"src/native/file_descriptor.cc"
|
|
6
|
+
"src/tuntap.cc"
|
|
8
7
|
],
|
|
9
8
|
"include_dirs": [
|
|
10
9
|
"<!@(node -p \"require('node-addon-api').include\")"
|
|
@@ -64,6 +63,8 @@
|
|
|
64
63
|
"conditions": [
|
|
65
64
|
["OS=='linux'", {
|
|
66
65
|
"sources": [
|
|
66
|
+
"src/native/file_descriptor.cc",
|
|
67
|
+
"src/native/posix_uv_poll_loop.cc",
|
|
67
68
|
"src/native/tun_backend_linux.cc"
|
|
68
69
|
],
|
|
69
70
|
"cflags": [
|
|
@@ -78,6 +79,8 @@
|
|
|
78
79
|
}],
|
|
79
80
|
["OS=='mac'", {
|
|
80
81
|
"sources": [
|
|
82
|
+
"src/native/file_descriptor.cc",
|
|
83
|
+
"src/native/posix_uv_poll_loop.cc",
|
|
81
84
|
"src/native/tun_backend_darwin.cc"
|
|
82
85
|
],
|
|
83
86
|
"xcode_settings": {
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
4
|
+
|
|
5
|
+
#include <string>
|
|
6
|
+
#include <utility>
|
|
7
|
+
#include <vector>
|
|
8
|
+
|
|
9
|
+
#include "file_descriptor.h"
|
|
10
|
+
#include "posix_uv_poll_loop.h"
|
|
11
|
+
#include "tun_backend.h"
|
|
12
|
+
|
|
13
|
+
// Shared base class for POSIX TUN backends (Darwin, Linux). Owns the file
|
|
14
|
+
// descriptor, the assigned interface name, and the libuv poll loop. Concrete
|
|
15
|
+
// subclasses implement only the platform-specific OpenDevice, ReadPacket, and
|
|
16
|
+
// WritePacket.
|
|
17
|
+
class PosixTunBackend : public TunPlatformBackend {
|
|
18
|
+
public:
|
|
19
|
+
void CloseDevice() override {
|
|
20
|
+
poll_loop_.Stop();
|
|
21
|
+
fd_.reset();
|
|
22
|
+
interface_name_.clear();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
bool IsOpen() const override { return fd_.is_valid(); }
|
|
26
|
+
|
|
27
|
+
bool StartReceiveLoop(uv_loop_t* loop,
|
|
28
|
+
size_t buffer_size,
|
|
29
|
+
PacketCallback on_packet,
|
|
30
|
+
ErrorCallback on_error,
|
|
31
|
+
std::string& error) override {
|
|
32
|
+
if (!fd_.is_valid()) {
|
|
33
|
+
error = "Device not open";
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return poll_loop_.Start(
|
|
37
|
+
loop,
|
|
38
|
+
fd_.get(),
|
|
39
|
+
buffer_size,
|
|
40
|
+
[this](size_t size, std::vector<uint8_t>& out, std::string& err) {
|
|
41
|
+
return ReadPacket(size, out, err);
|
|
42
|
+
},
|
|
43
|
+
std::move(on_packet),
|
|
44
|
+
std::move(on_error),
|
|
45
|
+
error);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void StopReceiveLoop() override { poll_loop_.Stop(); }
|
|
49
|
+
|
|
50
|
+
int GetNativeFd() const override { return fd_.get(); }
|
|
51
|
+
|
|
52
|
+
protected:
|
|
53
|
+
FileDescriptor fd_;
|
|
54
|
+
std::string interface_name_;
|
|
55
|
+
PosixUvPollLoop poll_loop_;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
#endif
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
2
|
+
|
|
3
|
+
#include "posix_uv_poll_loop.h"
|
|
4
|
+
|
|
5
|
+
#include <cstdio>
|
|
6
|
+
#include <errno.h>
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <utility>
|
|
10
|
+
|
|
11
|
+
PosixUvPollLoop::~PosixUvPollLoop() {
|
|
12
|
+
Stop();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
bool PosixUvPollLoop::Start(uv_loop_t* loop,
|
|
16
|
+
int fd,
|
|
17
|
+
size_t buffer_size,
|
|
18
|
+
ReadFn read_fn,
|
|
19
|
+
TunPlatformBackend::PacketCallback on_packet,
|
|
20
|
+
TunPlatformBackend::ErrorCallback on_error,
|
|
21
|
+
std::string& error) {
|
|
22
|
+
if (handle_) {
|
|
23
|
+
error = "Receive loop already started";
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
if (!loop || fd < 0 || buffer_size == 0) {
|
|
27
|
+
error = "Invalid receive-loop parameters";
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
auto state = std::make_unique<State>();
|
|
32
|
+
state->buffer_size = buffer_size;
|
|
33
|
+
state->read_fn = std::move(read_fn);
|
|
34
|
+
state->on_packet = std::move(on_packet);
|
|
35
|
+
state->on_error = std::move(on_error);
|
|
36
|
+
state->owner = this;
|
|
37
|
+
|
|
38
|
+
auto handle = std::make_unique<uv_poll_t>();
|
|
39
|
+
if (uv_poll_init(loop, handle.get(), fd) != 0) {
|
|
40
|
+
error = "Failed to initialize poll handle";
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handle->data = state.get();
|
|
45
|
+
if (uv_poll_start(handle.get(), UV_READABLE, &PosixUvPollLoop::OnPoll) != 0) {
|
|
46
|
+
uv_close(reinterpret_cast<uv_handle_t*>(handle.release()),
|
|
47
|
+
[](uv_handle_t* h) { delete reinterpret_cast<uv_poll_t*>(h); });
|
|
48
|
+
error = "Failed to start polling";
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
state_ = std::move(state);
|
|
53
|
+
handle_ = handle.release();
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
void PosixUvPollLoop::Stop() {
|
|
58
|
+
if (!handle_) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
uv_poll_stop(handle_);
|
|
63
|
+
handle_->data = nullptr;
|
|
64
|
+
uv_close(reinterpret_cast<uv_handle_t*>(handle_),
|
|
65
|
+
&PosixUvPollLoop::OnHandleClosed);
|
|
66
|
+
handle_ = nullptr;
|
|
67
|
+
state_.reset();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
void PosixUvPollLoop::OnPoll(uv_poll_t* handle, int status, int events) {
|
|
71
|
+
auto* state = static_cast<State*>(handle->data);
|
|
72
|
+
if (!state) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Tear down the loop and notify the owner of a terminal condition. The
|
|
77
|
+
// callback is copied locally first because Stop() destroys `state` (and the
|
|
78
|
+
// std::function it owns) synchronously.
|
|
79
|
+
auto handle_terminal = [&](const std::string& msg) {
|
|
80
|
+
auto cb = state->on_error;
|
|
81
|
+
PosixUvPollLoop* owner = state->owner;
|
|
82
|
+
if (owner) {
|
|
83
|
+
owner->Stop();
|
|
84
|
+
}
|
|
85
|
+
if (cb) {
|
|
86
|
+
cb(msg);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (status < 0) {
|
|
91
|
+
handle_terminal(std::string("Poll error: ") + uv_strerror(status));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (!(events & UV_READABLE) || !state->read_fn) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
std::vector<uint8_t> packet;
|
|
100
|
+
std::string error;
|
|
101
|
+
ReadPacketStatus rs = state->read_fn(state->buffer_size, packet, error);
|
|
102
|
+
|
|
103
|
+
switch (rs) {
|
|
104
|
+
case ReadPacketStatus::Data:
|
|
105
|
+
if (state->on_packet) {
|
|
106
|
+
state->on_packet(std::move(packet));
|
|
107
|
+
}
|
|
108
|
+
return;
|
|
109
|
+
case ReadPacketStatus::NoData:
|
|
110
|
+
return;
|
|
111
|
+
case ReadPacketStatus::Closed:
|
|
112
|
+
handle_terminal("Device closed");
|
|
113
|
+
return;
|
|
114
|
+
case ReadPacketStatus::Error:
|
|
115
|
+
handle_terminal(error);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
void PosixUvPollLoop::OnHandleClosed(uv_handle_t* handle) {
|
|
121
|
+
delete reinterpret_cast<uv_poll_t*>(handle);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
bool SetNonBlocking(int fd, std::string& error) {
|
|
125
|
+
int flags = fcntl(fd, F_GETFL, 0);
|
|
126
|
+
if (flags < 0) {
|
|
127
|
+
error = std::string("Failed to get file descriptor flags: ") + strerror(errno);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
131
|
+
error = std::string("Failed to set non-blocking mode: ") + strerror(errno);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#endif
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#if defined(__APPLE__) || defined(__linux__)
|
|
4
|
+
|
|
5
|
+
#include <cstddef>
|
|
6
|
+
#include <functional>
|
|
7
|
+
#include <memory>
|
|
8
|
+
#include <string>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
#include <uv.h>
|
|
12
|
+
|
|
13
|
+
#include "tun_backend.h"
|
|
14
|
+
|
|
15
|
+
class PosixUvPollLoop {
|
|
16
|
+
public:
|
|
17
|
+
using ReadFn = std::function<ReadPacketStatus(size_t,
|
|
18
|
+
std::vector<uint8_t>&,
|
|
19
|
+
std::string&)>;
|
|
20
|
+
|
|
21
|
+
PosixUvPollLoop() = default;
|
|
22
|
+
~PosixUvPollLoop();
|
|
23
|
+
|
|
24
|
+
PosixUvPollLoop(const PosixUvPollLoop&) = delete;
|
|
25
|
+
PosixUvPollLoop& operator=(const PosixUvPollLoop&) = delete;
|
|
26
|
+
|
|
27
|
+
bool Start(uv_loop_t* loop,
|
|
28
|
+
int fd,
|
|
29
|
+
size_t buffer_size,
|
|
30
|
+
ReadFn read_fn,
|
|
31
|
+
TunPlatformBackend::PacketCallback on_packet,
|
|
32
|
+
TunPlatformBackend::ErrorCallback on_error,
|
|
33
|
+
std::string& error);
|
|
34
|
+
|
|
35
|
+
void Stop();
|
|
36
|
+
|
|
37
|
+
private:
|
|
38
|
+
struct State {
|
|
39
|
+
size_t buffer_size = 0;
|
|
40
|
+
ReadFn read_fn;
|
|
41
|
+
TunPlatformBackend::PacketCallback on_packet;
|
|
42
|
+
TunPlatformBackend::ErrorCallback on_error;
|
|
43
|
+
PosixUvPollLoop* owner = nullptr;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
static void OnPoll(uv_poll_t* handle, int status, int events);
|
|
47
|
+
static void OnHandleClosed(uv_handle_t* handle);
|
|
48
|
+
|
|
49
|
+
uv_poll_t* handle_ = nullptr;
|
|
50
|
+
std::unique_ptr<State> state_;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
bool SetNonBlocking(int fd, std::string& error);
|
|
54
|
+
|
|
55
|
+
#endif
|
package/src/native/tun_backend.h
CHANGED
|
@@ -6,17 +6,19 @@
|
|
|
6
6
|
|
|
7
7
|
#include <cstddef>
|
|
8
8
|
#include <cstdint>
|
|
9
|
+
#include <functional>
|
|
9
10
|
#include <memory>
|
|
10
11
|
#include <string>
|
|
11
|
-
#include <sys/types.h>
|
|
12
12
|
#include <vector>
|
|
13
13
|
|
|
14
|
-
#
|
|
14
|
+
#ifdef _WIN32
|
|
15
|
+
#include <BaseTsd.h>
|
|
16
|
+
using ssize_t = SSIZE_T;
|
|
17
|
+
#else
|
|
18
|
+
#include <sys/types.h>
|
|
19
|
+
#endif
|
|
15
20
|
|
|
16
|
-
|
|
17
|
-
FileDescriptor fd;
|
|
18
|
-
std::string interface_name;
|
|
19
|
-
};
|
|
21
|
+
#include <uv.h>
|
|
20
22
|
|
|
21
23
|
enum class ReadPacketStatus {
|
|
22
24
|
Data,
|
|
@@ -27,11 +29,32 @@ enum class ReadPacketStatus {
|
|
|
27
29
|
|
|
28
30
|
class TunPlatformBackend {
|
|
29
31
|
public:
|
|
32
|
+
using PacketCallback = std::function<void(std::vector<uint8_t>)>;
|
|
33
|
+
using ErrorCallback = std::function<void(const std::string&)>;
|
|
34
|
+
|
|
30
35
|
virtual ~TunPlatformBackend() = default;
|
|
31
|
-
|
|
32
|
-
virtual
|
|
33
|
-
|
|
36
|
+
|
|
37
|
+
virtual bool OpenDevice(const std::string& requested_name,
|
|
38
|
+
std::string& out_interface_name,
|
|
39
|
+
std::string& error) = 0;
|
|
40
|
+
virtual void CloseDevice() = 0;
|
|
41
|
+
virtual bool IsOpen() const = 0;
|
|
42
|
+
|
|
43
|
+
virtual ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
44
|
+
std::vector<uint8_t>& out,
|
|
45
|
+
std::string& error) = 0;
|
|
46
|
+
virtual ssize_t WritePacket(const uint8_t* data,
|
|
47
|
+
size_t length,
|
|
48
|
+
std::string& error) = 0;
|
|
49
|
+
|
|
50
|
+
virtual bool StartReceiveLoop(uv_loop_t* loop,
|
|
51
|
+
size_t buffer_size,
|
|
52
|
+
PacketCallback on_packet,
|
|
53
|
+
ErrorCallback on_error,
|
|
54
|
+
std::string& error) = 0;
|
|
55
|
+
virtual void StopReceiveLoop() = 0;
|
|
56
|
+
|
|
57
|
+
virtual int GetNativeFd() const { return -1; }
|
|
34
58
|
};
|
|
35
59
|
|
|
36
60
|
std::unique_ptr<TunPlatformBackend> CreatePlatformBackend();
|
|
37
|
-
|
|
@@ -14,30 +14,23 @@
|
|
|
14
14
|
#include <netinet/in.h>
|
|
15
15
|
#include <netinet6/in6_var.h>
|
|
16
16
|
|
|
17
|
-
#include <
|
|
17
|
+
#include <utility>
|
|
18
|
+
|
|
19
|
+
#include "file_descriptor.h"
|
|
20
|
+
#include "posix_tun_backend.h"
|
|
21
|
+
#include "posix_uv_poll_loop.h"
|
|
18
22
|
|
|
19
23
|
#define UTUN_CONTROL_NAME "com.apple.net.utun_control"
|
|
20
24
|
|
|
21
25
|
namespace {
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
int flags = fcntl(fd, F_GETFL, 0);
|
|
25
|
-
if (flags < 0) {
|
|
26
|
-
error = std::string("Failed to get file descriptor flags: ") + strerror(errno);
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
30
|
-
error = std::string("Failed to set non-blocking mode: ") + strerror(errno);
|
|
31
|
-
return false;
|
|
32
|
-
}
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
class DarwinTunBackend : public TunPlatformBackend {
|
|
27
|
+
class DarwinTunBackend : public PosixTunBackend {
|
|
37
28
|
public:
|
|
38
29
|
static constexpr size_t kUtunHeaderSize = 4;
|
|
39
30
|
|
|
40
|
-
bool OpenDevice(const std::string& requested_name,
|
|
31
|
+
bool OpenDevice(const std::string& requested_name,
|
|
32
|
+
std::string& out_interface_name,
|
|
33
|
+
std::string& error) override {
|
|
41
34
|
struct ctl_info ctl_info;
|
|
42
35
|
struct sockaddr_ctl socket_addr;
|
|
43
36
|
|
|
@@ -84,14 +77,22 @@ public:
|
|
|
84
77
|
return false;
|
|
85
78
|
}
|
|
86
79
|
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
fd_ = std::move(temp_fd);
|
|
81
|
+
interface_name_ = std::string(interface_name);
|
|
82
|
+
out_interface_name = interface_name_;
|
|
89
83
|
return true;
|
|
90
84
|
}
|
|
91
85
|
|
|
92
|
-
ReadPacketStatus ReadPacket(
|
|
86
|
+
ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
87
|
+
std::vector<uint8_t>& out,
|
|
88
|
+
std::string& error) override {
|
|
89
|
+
if (!fd_.is_valid()) {
|
|
90
|
+
error = "Device not open";
|
|
91
|
+
return ReadPacketStatus::Error;
|
|
92
|
+
}
|
|
93
|
+
|
|
93
94
|
out.resize(max_payload_size + kUtunHeaderSize);
|
|
94
|
-
ssize_t bytes_read = read(
|
|
95
|
+
ssize_t bytes_read = read(fd_.get(), out.data(), out.size());
|
|
95
96
|
if (bytes_read < 0) {
|
|
96
97
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
97
98
|
out.clear();
|
|
@@ -116,21 +117,28 @@ public:
|
|
|
116
117
|
return ReadPacketStatus::Data;
|
|
117
118
|
}
|
|
118
119
|
|
|
119
|
-
ssize_t WritePacket(
|
|
120
|
+
ssize_t WritePacket(const uint8_t* data,
|
|
121
|
+
size_t length,
|
|
122
|
+
std::string& error) override {
|
|
123
|
+
if (!fd_.is_valid()) {
|
|
124
|
+
error = "Device not open";
|
|
125
|
+
return -1;
|
|
126
|
+
}
|
|
127
|
+
|
|
120
128
|
std::vector<uint8_t> frame(length + kUtunHeaderSize);
|
|
121
129
|
uint32_t family = htonl(AF_INET6);
|
|
122
130
|
memcpy(frame.data(), &family, kUtunHeaderSize);
|
|
123
131
|
memcpy(frame.data() + kUtunHeaderSize, data, length);
|
|
124
132
|
|
|
125
|
-
ssize_t bytes_written = write(
|
|
133
|
+
ssize_t bytes_written = write(fd_.get(), frame.data(), frame.size());
|
|
126
134
|
if (bytes_written < 0) {
|
|
127
135
|
error = std::string("Write error: ") + strerror(errno);
|
|
128
136
|
return -1;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
139
|
return bytes_written > static_cast<ssize_t>(kUtunHeaderSize)
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
? bytes_written - static_cast<ssize_t>(kUtunHeaderSize)
|
|
141
|
+
: 0;
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
private:
|
|
@@ -168,4 +176,3 @@ std::unique_ptr<TunPlatformBackend> CreatePlatformBackend() {
|
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
#endif
|
|
171
|
-
|
|
@@ -12,40 +12,35 @@
|
|
|
12
12
|
#include <linux/if.h>
|
|
13
13
|
#include <linux/if_tun.h>
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
#include <utility>
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0) {
|
|
24
|
-
error = std::string("Failed to set non-blocking mode: ") + strerror(errno);
|
|
25
|
-
return false;
|
|
26
|
-
}
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
17
|
+
#include "file_descriptor.h"
|
|
18
|
+
#include "posix_tun_backend.h"
|
|
19
|
+
#include "posix_uv_poll_loop.h"
|
|
20
|
+
|
|
21
|
+
namespace {
|
|
29
22
|
|
|
30
23
|
constexpr const char* kTunDevicePath = "/dev/net/tun";
|
|
31
24
|
|
|
32
|
-
class LinuxTunBackend : public
|
|
25
|
+
class LinuxTunBackend : public PosixTunBackend {
|
|
33
26
|
public:
|
|
34
|
-
bool OpenDevice(const std::string& requested_name,
|
|
27
|
+
bool OpenDevice(const std::string& requested_name,
|
|
28
|
+
std::string& out_interface_name,
|
|
29
|
+
std::string& error) override {
|
|
35
30
|
struct stat statbuf;
|
|
36
31
|
if (stat(kTunDevicePath, &statbuf) != 0) {
|
|
37
32
|
error =
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
"TUN/TAP device not available: /dev/net/tun does not exist. "
|
|
34
|
+
"Please ensure the TUN/TAP kernel module is loaded (modprobe tun).";
|
|
40
35
|
return false;
|
|
41
36
|
}
|
|
42
37
|
|
|
43
38
|
FileDescriptor temp_fd(open(kTunDevicePath, O_RDWR));
|
|
44
39
|
if (!temp_fd.is_valid()) {
|
|
45
40
|
error =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
std::string("Failed to open ") + kTunDevicePath + ": " + strerror(errno) +
|
|
42
|
+
". This usually means you don't have sufficient permissions. "
|
|
43
|
+
"Try running with sudo or add your user to the 'tun' group.";
|
|
49
44
|
return false;
|
|
50
45
|
}
|
|
51
46
|
|
|
@@ -67,14 +62,22 @@ public:
|
|
|
67
62
|
return false;
|
|
68
63
|
}
|
|
69
64
|
|
|
70
|
-
|
|
71
|
-
|
|
65
|
+
fd_ = std::move(temp_fd);
|
|
66
|
+
interface_name_ = std::string(ifr.ifr_name);
|
|
67
|
+
out_interface_name = interface_name_;
|
|
72
68
|
return true;
|
|
73
69
|
}
|
|
74
70
|
|
|
75
|
-
ReadPacketStatus ReadPacket(
|
|
71
|
+
ReadPacketStatus ReadPacket(size_t max_payload_size,
|
|
72
|
+
std::vector<uint8_t>& out,
|
|
73
|
+
std::string& error) override {
|
|
74
|
+
if (!fd_.is_valid()) {
|
|
75
|
+
error = "Device not open";
|
|
76
|
+
return ReadPacketStatus::Error;
|
|
77
|
+
}
|
|
78
|
+
|
|
76
79
|
out.resize(max_payload_size);
|
|
77
|
-
ssize_t bytes_read = read(
|
|
80
|
+
ssize_t bytes_read = read(fd_.get(), out.data(), out.size());
|
|
78
81
|
if (bytes_read < 0) {
|
|
79
82
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
80
83
|
out.clear();
|
|
@@ -92,8 +95,14 @@ public:
|
|
|
92
95
|
return ReadPacketStatus::Data;
|
|
93
96
|
}
|
|
94
97
|
|
|
95
|
-
ssize_t WritePacket(
|
|
96
|
-
|
|
98
|
+
ssize_t WritePacket(const uint8_t* data,
|
|
99
|
+
size_t length,
|
|
100
|
+
std::string& error) override {
|
|
101
|
+
if (!fd_.is_valid()) {
|
|
102
|
+
error = "Device not open";
|
|
103
|
+
return -1;
|
|
104
|
+
}
|
|
105
|
+
ssize_t bytes_written = write(fd_.get(), data, length);
|
|
97
106
|
if (bytes_written < 0) {
|
|
98
107
|
error = std::string("Write error: ") + strerror(errno);
|
|
99
108
|
return -1;
|
|
@@ -109,4 +118,3 @@ std::unique_ptr<TunPlatformBackend> CreatePlatformBackend() {
|
|
|
109
118
|
}
|
|
110
119
|
|
|
111
120
|
#endif
|
|
112
|
-
|
package/src/tuntap.cc
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#include <napi.h>
|
|
2
|
-
#include <uv.h>
|
|
3
2
|
|
|
4
|
-
#include <string>
|
|
5
|
-
#include <vector>
|
|
6
|
-
#include <memory>
|
|
7
|
-
#include <mutex>
|
|
8
3
|
#include <atomic>
|
|
9
4
|
#include <cstdio>
|
|
5
|
+
#include <memory>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <string>
|
|
8
|
+
#include <utility>
|
|
9
|
+
#include <vector>
|
|
10
10
|
|
|
11
|
-
#include "native/file_descriptor.h"
|
|
12
11
|
#include "native/tun_backend.h"
|
|
13
12
|
|
|
14
13
|
class TunDevice : public Napi::ObjectWrap<TunDevice> {
|
|
@@ -30,24 +29,22 @@ private:
|
|
|
30
29
|
Napi::Value GetFd(const Napi::CallbackInfo& info);
|
|
31
30
|
Napi::Value StartPolling(const Napi::CallbackInfo& info);
|
|
32
31
|
|
|
33
|
-
FileDescriptor fd_;
|
|
34
|
-
std::string name_;
|
|
35
32
|
std::unique_ptr<TunPlatformBackend> backend_;
|
|
33
|
+
std::string requested_name_;
|
|
34
|
+
std::string interface_name_;
|
|
36
35
|
std::atomic<bool> is_open_;
|
|
37
36
|
std::mutex device_mutex_;
|
|
38
37
|
|
|
39
|
-
uv_poll_t* poll_handle_ = nullptr;
|
|
40
38
|
Napi::ThreadSafeFunction tsfn_;
|
|
39
|
+
std::atomic<bool> polling_;
|
|
41
40
|
static constexpr size_t MAX_POLL_BUFFER = 65535;
|
|
42
|
-
size_t poll_buffer_size_ = MAX_POLL_BUFFER;
|
|
43
41
|
|
|
44
|
-
void
|
|
45
|
-
|
|
42
|
+
void StopPollingLocked();
|
|
43
|
+
void ReleaseTsfnLocked();
|
|
46
44
|
};
|
|
47
45
|
|
|
48
46
|
Napi::FunctionReference TunDevice::constructor;
|
|
49
47
|
|
|
50
|
-
// Defines and exports the JS class constructor: new TunDevice(name?)
|
|
51
48
|
Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
52
49
|
Napi::HandleScope scope(env);
|
|
53
50
|
|
|
@@ -68,25 +65,24 @@ Napi::Object TunDevice::Init(Napi::Env env, Napi::Object exports) {
|
|
|
68
65
|
return exports;
|
|
69
66
|
}
|
|
70
67
|
|
|
71
|
-
// Creates a TunDevice wrapper; optional first arg is requested interface name.
|
|
72
68
|
TunDevice::TunDevice(const Napi::CallbackInfo& info)
|
|
73
|
-
|
|
69
|
+
: Napi::ObjectWrap<TunDevice>(info),
|
|
70
|
+
backend_(CreatePlatformBackend()),
|
|
71
|
+
is_open_(false),
|
|
72
|
+
polling_(false) {
|
|
74
73
|
Napi::Env env = info.Env();
|
|
75
74
|
Napi::HandleScope scope(env);
|
|
76
75
|
|
|
77
76
|
if (info.Length() > 0 && info[0].IsString()) {
|
|
78
|
-
|
|
77
|
+
requested_name_ = info[0].As<Napi::String>().Utf8Value();
|
|
79
78
|
}
|
|
80
79
|
}
|
|
81
80
|
|
|
82
|
-
// Ensures fd/poll resources are closed when object is destroyed.
|
|
83
81
|
TunDevice::~TunDevice() {
|
|
84
82
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
85
83
|
CloseInternal();
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
// JS: open() -> boolean
|
|
89
|
-
// Opens the backend device and configures the fd as non-blocking.
|
|
90
86
|
Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
91
87
|
Napi::Env env = info.Env();
|
|
92
88
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -100,22 +96,18 @@ Napi::Value TunDevice::Open(const Napi::CallbackInfo& info) {
|
|
|
100
96
|
return Napi::Boolean::New(env, false);
|
|
101
97
|
}
|
|
102
98
|
|
|
103
|
-
OpenResult result;
|
|
104
99
|
std::string error;
|
|
105
|
-
|
|
100
|
+
std::string assigned_name;
|
|
101
|
+
if (!backend_->OpenDevice(requested_name_, assigned_name, error)) {
|
|
106
102
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
107
103
|
return Napi::Boolean::New(env, false);
|
|
108
104
|
}
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
name_ = result.interface_name;
|
|
106
|
+
interface_name_ = std::move(assigned_name);
|
|
112
107
|
is_open_ = true;
|
|
113
|
-
|
|
114
108
|
return Napi::Boolean::New(env, true);
|
|
115
109
|
}
|
|
116
110
|
|
|
117
|
-
// JS: close() -> boolean
|
|
118
|
-
// Safely closes device resources; calling multiple times is allowed.
|
|
119
111
|
Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
120
112
|
Napi::Env env = info.Env();
|
|
121
113
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
@@ -123,13 +115,11 @@ Napi::Value TunDevice::Close(const Napi::CallbackInfo& info) {
|
|
|
123
115
|
return Napi::Boolean::New(env, true);
|
|
124
116
|
}
|
|
125
117
|
|
|
126
|
-
// JS: read(bufferSize?) -> Buffer
|
|
127
|
-
// Reads one payload packet, or returns an empty Buffer when no data is available.
|
|
128
118
|
Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
129
119
|
Napi::Env env = info.Env();
|
|
130
120
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
131
121
|
|
|
132
|
-
if (!is_open_ || !
|
|
122
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
133
123
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
134
124
|
return env.Null();
|
|
135
125
|
}
|
|
@@ -145,25 +135,22 @@ Napi::Value TunDevice::Read(const Napi::CallbackInfo& info) {
|
|
|
145
135
|
|
|
146
136
|
std::vector<uint8_t> packet;
|
|
147
137
|
std::string error;
|
|
148
|
-
ReadPacketStatus
|
|
149
|
-
if (
|
|
138
|
+
ReadPacketStatus rs = backend_->ReadPacket(buffer_size, packet, error);
|
|
139
|
+
if (rs == ReadPacketStatus::Error) {
|
|
150
140
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
151
141
|
return env.Null();
|
|
152
142
|
}
|
|
153
|
-
if (
|
|
143
|
+
if (rs == ReadPacketStatus::NoData || rs == ReadPacketStatus::Closed) {
|
|
154
144
|
return Napi::Buffer<uint8_t>::New(env, 0);
|
|
155
145
|
}
|
|
156
|
-
|
|
157
146
|
return Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size());
|
|
158
147
|
}
|
|
159
148
|
|
|
160
|
-
// JS: write(buffer) -> number
|
|
161
|
-
// Writes one packet and returns payload bytes accepted by the backend.
|
|
162
149
|
Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
163
150
|
Napi::Env env = info.Env();
|
|
164
151
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
165
152
|
|
|
166
|
-
if (!is_open_ || !
|
|
153
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
167
154
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
168
155
|
return Napi::Number::New(env, -1);
|
|
169
156
|
}
|
|
@@ -178,35 +165,29 @@ Napi::Value TunDevice::Write(const Napi::CallbackInfo& info) {
|
|
|
178
165
|
size_t length = buffer.Length();
|
|
179
166
|
|
|
180
167
|
std::string error;
|
|
181
|
-
ssize_t bytes_written = backend_->WritePacket(
|
|
168
|
+
ssize_t bytes_written = backend_->WritePacket(data, length, error);
|
|
182
169
|
if (bytes_written < 0) {
|
|
183
170
|
Napi::Error::New(env, error).ThrowAsJavaScriptException();
|
|
184
171
|
return Napi::Number::New(env, -1);
|
|
185
172
|
}
|
|
186
|
-
return Napi::Number::New(env, bytes_written);
|
|
173
|
+
return Napi::Number::New(env, static_cast<double>(bytes_written));
|
|
187
174
|
}
|
|
188
175
|
|
|
189
|
-
// JS: getName() -> string
|
|
190
|
-
// Returns the assigned interface name after open().
|
|
191
176
|
Napi::Value TunDevice::GetName(const Napi::CallbackInfo& info) {
|
|
192
177
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
193
|
-
return Napi::String::New(info.Env(),
|
|
178
|
+
return Napi::String::New(info.Env(), interface_name_);
|
|
194
179
|
}
|
|
195
180
|
|
|
196
|
-
// JS: getFd() -> number
|
|
197
|
-
// Returns the native file descriptor, or -1 before open()/after close().
|
|
198
181
|
Napi::Value TunDevice::GetFd(const Napi::CallbackInfo& info) {
|
|
199
182
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
200
|
-
return Napi::Number::New(info.Env(),
|
|
183
|
+
return Napi::Number::New(info.Env(), backend_ ? backend_->GetNativeFd() : -1);
|
|
201
184
|
}
|
|
202
185
|
|
|
203
|
-
// JS: startPolling(callback, bufferSize?) -> void
|
|
204
|
-
// Starts libuv polling and invokes callback with packet payload Buffers.
|
|
205
186
|
Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
206
187
|
Napi::Env env = info.Env();
|
|
207
188
|
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
208
189
|
|
|
209
|
-
if (!is_open_ || !
|
|
190
|
+
if (!is_open_ || !backend_ || !backend_->IsOpen()) {
|
|
210
191
|
Napi::Error::New(env, "Device not open").ThrowAsJavaScriptException();
|
|
211
192
|
return env.Null();
|
|
212
193
|
}
|
|
@@ -216,134 +197,91 @@ Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
|
|
|
216
197
|
return env.Null();
|
|
217
198
|
}
|
|
218
199
|
|
|
219
|
-
|
|
200
|
+
StopPollingLocked();
|
|
220
201
|
|
|
221
|
-
|
|
222
|
-
poll_buffer_size_ = MAX_POLL_BUFFER;
|
|
202
|
+
size_t buffer_size = MAX_POLL_BUFFER;
|
|
223
203
|
if (info.Length() > 1 && info[1].IsNumber()) {
|
|
224
204
|
auto size = info[1].As<Napi::Number>().Uint32Value();
|
|
225
205
|
if (size == 0 || size > MAX_POLL_BUFFER) {
|
|
226
206
|
Napi::RangeError::New(env, "Buffer size must be between 1 and " + std::to_string(MAX_POLL_BUFFER)).ThrowAsJavaScriptException();
|
|
227
207
|
return env.Null();
|
|
228
208
|
}
|
|
229
|
-
|
|
209
|
+
buffer_size = size;
|
|
230
210
|
}
|
|
231
211
|
|
|
232
212
|
tsfn_ = Napi::ThreadSafeFunction::New(
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
);
|
|
213
|
+
env,
|
|
214
|
+
info[0].As<Napi::Function>(),
|
|
215
|
+
"TunDeviceDataCallback",
|
|
216
|
+
0,
|
|
217
|
+
1);
|
|
239
218
|
|
|
240
219
|
uv_loop_t* loop = nullptr;
|
|
241
220
|
napi_status napi_st = napi_get_uv_event_loop(env, &loop);
|
|
242
221
|
if (napi_st != napi_ok || loop == nullptr) {
|
|
243
|
-
|
|
244
|
-
tsfn_ = nullptr;
|
|
222
|
+
ReleaseTsfnLocked();
|
|
245
223
|
Napi::Error::New(env, "Failed to acquire event loop").ThrowAsJavaScriptException();
|
|
246
224
|
return env.Null();
|
|
247
225
|
}
|
|
248
226
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
227
|
+
Napi::ThreadSafeFunction tsfn = tsfn_;
|
|
228
|
+
auto packet_cb = [tsfn](std::vector<uint8_t> packet) mutable {
|
|
229
|
+
tsfn.BlockingCall(
|
|
230
|
+
[packet = std::move(packet)](Napi::Env env, Napi::Function jsCallback) {
|
|
231
|
+
if (env == nullptr || jsCallback.IsEmpty()) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
jsCallback.Call({Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size())});
|
|
235
|
+
});
|
|
236
|
+
};
|
|
237
|
+
// Terminal errors from the receive loop (poll error, device closed, read
|
|
238
|
+
// error) call back here so the JS-side polling_ flag and TSFN are released
|
|
239
|
+
// promptly. Callback runs on the libuv thread, which only fires between JS
|
|
240
|
+
// ticks, so acquiring `device_mutex_` is safe.
|
|
241
|
+
auto error_cb = [this](const std::string& message) {
|
|
242
|
+
fprintf(stderr, "tuntap receive loop error: %s\n", message.c_str());
|
|
243
|
+
std::lock_guard<std::mutex> lock(device_mutex_);
|
|
244
|
+
polling_ = false;
|
|
245
|
+
ReleaseTsfnLocked();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
std::string start_error;
|
|
249
|
+
if (!backend_->StartReceiveLoop(loop, buffer_size, std::move(packet_cb), std::move(error_cb), start_error)) {
|
|
250
|
+
ReleaseTsfnLocked();
|
|
251
|
+
Napi::Error::New(env, start_error).ThrowAsJavaScriptException();
|
|
266
252
|
return env.Null();
|
|
267
253
|
}
|
|
268
254
|
|
|
269
|
-
|
|
255
|
+
polling_ = true;
|
|
270
256
|
return env.Undefined();
|
|
271
257
|
}
|
|
272
258
|
|
|
273
|
-
// Node-API module entrypoint.
|
|
274
259
|
Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
275
260
|
return TunDevice::Init(env, exports);
|
|
276
261
|
}
|
|
277
262
|
|
|
278
263
|
NODE_API_MODULE(tuntap, Init)
|
|
279
|
-
// #endregion
|
|
280
|
-
|
|
281
|
-
// #region Private implementation details
|
|
282
264
|
|
|
283
265
|
void TunDevice::CloseInternal() {
|
|
284
266
|
if (is_open_.exchange(false)) {
|
|
285
|
-
|
|
286
|
-
|
|
267
|
+
StopPollingLocked();
|
|
268
|
+
if (backend_) {
|
|
269
|
+
backend_->CloseDevice();
|
|
270
|
+
}
|
|
271
|
+
interface_name_.clear();
|
|
287
272
|
}
|
|
288
273
|
}
|
|
289
274
|
|
|
290
|
-
void TunDevice::
|
|
291
|
-
if (
|
|
292
|
-
|
|
293
|
-
// Must use uv_close before freeing a libuv handle
|
|
294
|
-
uv_close(reinterpret_cast<uv_handle_t*>(poll_handle_), [](uv_handle_t* handle) {
|
|
295
|
-
delete reinterpret_cast<uv_poll_t*>(handle);
|
|
296
|
-
});
|
|
297
|
-
poll_handle_ = nullptr;
|
|
275
|
+
void TunDevice::StopPollingLocked() {
|
|
276
|
+
if (polling_.exchange(false) && backend_) {
|
|
277
|
+
backend_->StopReceiveLoop();
|
|
298
278
|
}
|
|
279
|
+
ReleaseTsfnLocked();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
void TunDevice::ReleaseTsfnLocked() {
|
|
299
283
|
if (tsfn_) {
|
|
300
284
|
tsfn_.Release();
|
|
301
285
|
tsfn_ = nullptr;
|
|
302
286
|
}
|
|
303
287
|
}
|
|
304
|
-
|
|
305
|
-
void TunDevice::PollCallback(uv_poll_t* handle, int status, int events) {
|
|
306
|
-
if (status < 0) {
|
|
307
|
-
fprintf(stderr, "tuntap poll error: %s\n", uv_strerror(status));
|
|
308
|
-
auto* self = static_cast<TunDevice*>(handle->data);
|
|
309
|
-
if (self) {
|
|
310
|
-
self->StopPolling();
|
|
311
|
-
}
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
if (!(events & UV_READABLE)) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
auto* self = static_cast<TunDevice*>(handle->data);
|
|
320
|
-
if (!self || !self->is_open_.load() || !self->fd_.is_valid()) {
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
std::vector<uint8_t> packet;
|
|
325
|
-
std::string error;
|
|
326
|
-
ReadPacketStatus read_status = self->backend_->ReadPacket(self->fd_.get(), self->poll_buffer_size_, packet, error);
|
|
327
|
-
// Backend reported an unrecoverable read failure; stop polling this fd.
|
|
328
|
-
if (read_status == ReadPacketStatus::Error) {
|
|
329
|
-
fprintf(stderr, "tuntap read error: %s\n", error.c_str());
|
|
330
|
-
self->StopPolling();
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
// EOF/peer close: device is no longer readable, so tear down polling.
|
|
334
|
-
if (read_status == ReadPacketStatus::Closed) {
|
|
335
|
-
self->StopPolling();
|
|
336
|
-
return;
|
|
337
|
-
}
|
|
338
|
-
// Transient empty read (e.g. EAGAIN): keep poll active and wait for next event.
|
|
339
|
-
if (read_status == ReadPacketStatus::NoData) {
|
|
340
|
-
return;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
self->tsfn_.BlockingCall(
|
|
344
|
-
[packet = std::move(packet)](Napi::Env env, Napi::Function jsCallback) {
|
|
345
|
-
if (env == nullptr || jsCallback.IsEmpty()) return;
|
|
346
|
-
jsCallback.Call({ Napi::Buffer<uint8_t>::Copy(env, packet.data(), packet.size()) });
|
|
347
|
-
}
|
|
348
|
-
);
|
|
349
|
-
}
|