appium-ios-tuntap 0.4.1 → 0.4.2

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 CHANGED
@@ -1,3 +1,9 @@
1
+ ## [0.4.2](https://github.com/appium/appium-ios-tuntap/compare/v0.4.1...v0.4.2) (2026-06-01)
2
+
3
+ ### Miscellaneous Chores
4
+
5
+ * Tune further tunnel perf ([#46](https://github.com/appium/appium-ios-tuntap/issues/46)) ([d422937](https://github.com/appium/appium-ios-tuntap/commit/d422937a47b16bf1c984030772d1de6e555609e6))
6
+
1
7
  ## [0.4.1](https://github.com/appium/appium-ios-tuntap/compare/v0.4.0...v0.4.1) (2026-05-31)
2
8
 
3
9
  ### Bug Fixes
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Append socket/TUN chunks without repeated Buffer.concat growth copies.
3
+ */
4
+ export declare function appendBuffer(existing: Buffer, chunk: Buffer): Buffer;
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Append socket/TUN chunks without repeated Buffer.concat growth copies.
3
+ */
4
+ export function appendBuffer(existing, chunk) {
5
+ if (chunk.length === 0) {
6
+ return existing;
7
+ }
8
+ if (existing.length === 0) {
9
+ return Buffer.from(chunk);
10
+ }
11
+ const combined = Buffer.allocUnsafe(existing.length + chunk.length);
12
+ existing.copy(combined, 0);
13
+ chunk.copy(combined, existing.length);
14
+ return combined;
15
+ }
@@ -3,6 +3,7 @@ import { TunTap } from '../TunTap.js';
3
3
  import { EventEmitter } from 'node:events';
4
4
  import { Buffer } from 'node:buffer';
5
5
  import { CD_TUNNEL_HANDSHAKE_TIMEOUT_MS, CD_TUNNEL_HEADER_SIZE, CD_TUNNEL_MAGIC, CD_TUNNEL_MAGIC_SIZE, CD_TUNNEL_MTU, IPV6_HEADER_SIZE, IPV6_VERSION, IPPROTO_TCP, IPPROTO_UDP, } from './constants.js';
6
+ import { appendBuffer } from './buffer-utils.js';
6
7
  /**
7
8
  * Bridges a CoreDevice tunnel `Socket` and a {@link TunTap} interface: IPv6 framing, TUN I/O, and packet fan-out.
8
9
  * Emits {@link TunnelManagerEvents} (currently `data` with {@link PacketData}) for TCP/UDP packets, same as registered consumers.
@@ -125,14 +126,15 @@ export class TunnelManager extends EventEmitter {
125
126
  }
126
127
  this.deviceConn = deviceConn;
127
128
  log.debug(`Starting bidirectional data forwarding for ${this.tun.name}`);
129
+ deviceConn.setNoDelay(true);
130
+ deviceConn.setKeepAlive(true, 1000);
128
131
  // Handle data from the device connection
129
132
  deviceConn.on('data', (data) => {
130
133
  if (this.cancelled) {
131
134
  return;
132
135
  }
133
136
  try {
134
- // Add data to buffer
135
- this.buffer = Buffer.concat([this.buffer, data]);
137
+ this.buffer = appendBuffer(this.buffer, data);
136
138
  // Process IPv6 packets
137
139
  this.processBuffer();
138
140
  }
@@ -258,10 +260,10 @@ export class TunnelManager extends EventEmitter {
258
260
  if (this.cancelled || !data.length || deviceConn.destroyed) {
259
261
  return;
260
262
  }
261
- if (data.length >= IPV6_HEADER_SIZE) {
263
+ if (this.hasPacketTap() && data.length >= IPV6_HEADER_SIZE) {
262
264
  log.debug(`TUN → Device: ${data.length} bytes, IPv6 src=${formatIPv6Address(data.subarray(8, 24))}, dst=${formatIPv6Address(data.subarray(24, 40))}`);
263
265
  }
264
- else {
266
+ else if (this.hasPacketTap()) {
265
267
  log.debug(`TUN → Device: ${data.length} bytes (too small for IPv6 header)`);
266
268
  }
267
269
  deviceConn.write(data);
@@ -401,7 +403,7 @@ function readCdTunnelResponse(socket, timeoutMs) {
401
403
  };
402
404
  const onData = (chunk) => {
403
405
  log.debug('Received data chunk:', chunk.length, 'bytes');
404
- buffer = Buffer.concat([buffer, chunk]);
406
+ buffer = appendBuffer(buffer, chunk);
405
407
  if (buffer.length >= CD_TUNNEL_HEADER_SIZE) {
406
408
  const payloadLength = buffer.readUInt16BE(CD_TUNNEL_MAGIC_SIZE);
407
409
  log.debug('Expected total packet length:', CD_TUNNEL_HEADER_SIZE + payloadLength, 'current buffer:', buffer.length);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appium-ios-tuntap",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Native TUN/TAP interface module for Node.js",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -91,8 +91,11 @@ public:
91
91
  return ReadPacketStatus::Error;
92
92
  }
93
93
 
94
- out.resize(max_payload_size + kUtunHeaderSize);
95
- ssize_t bytes_read = read(fd_.get(), out.data(), out.size());
94
+ const size_t read_cap = max_payload_size + kUtunHeaderSize;
95
+ if (read_frame_.size() < read_cap) {
96
+ read_frame_.resize(read_cap);
97
+ }
98
+ ssize_t bytes_read = read(fd_.get(), read_frame_.data(), read_cap);
96
99
  if (bytes_read < 0) {
97
100
  if (errno == EAGAIN || errno == EWOULDBLOCK) {
98
101
  out.clear();
@@ -111,9 +114,8 @@ public:
111
114
  }
112
115
 
113
116
  const auto payload_len = static_cast<size_t>(bytes_read - kUtunHeaderSize);
114
- // Collapse the utun 4-byte address-family prefix in-place.
115
- memmove(out.data(), out.data() + kUtunHeaderSize, payload_len);
116
117
  out.resize(payload_len);
118
+ memcpy(out.data(), read_frame_.data() + kUtunHeaderSize, payload_len);
117
119
  return ReadPacketStatus::Data;
118
120
  }
119
121
 
@@ -168,6 +170,7 @@ private:
168
170
  return false;
169
171
  }
170
172
 
173
+ std::vector<uint8_t> read_frame_;
171
174
  std::vector<uint8_t> write_frame_;
172
175
  };
173
176
 
package/src/tuntap.cc CHANGED
@@ -209,12 +209,14 @@ Napi::Value TunDevice::StartPolling(const Napi::CallbackInfo& info) {
209
209
  buffer_size = size;
210
210
  }
211
211
 
212
+ // Queue depth > 1 lets the poll thread post the next packet while JS is still
213
+ // handling the previous callback (still serialized on the main thread).
212
214
  tsfn_ = Napi::ThreadSafeFunction::New(
213
215
  env,
214
216
  info[0].As<Napi::Function>(),
215
217
  "TunDeviceDataCallback",
216
218
  0,
217
- 1);
219
+ 8);
218
220
 
219
221
  uv_loop_t* loop = nullptr;
220
222
  napi_status napi_st = napi_get_uv_event_loop(env, &loop);