Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 8545fb2

Browse filesBrowse files
jasnelladdaleax
authored andcommitted
test: add common/udppair utility
Extracted from the QUIC PR. This adds a utility used to deterministically test UDP traffic. It is currently only used by the experimental QUIC implementation. Separated out on request to make review easier. PR-URL: #33380 Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
1 parent 0affe86 commit 8545fb2
Copy full SHA for 8545fb2

File tree

Expand file treeCollapse file tree

8 files changed

+342
-0
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

8 files changed

+342
-0
lines changed
Open diff view settings
Collapse file

‎node.gyp‎

Copy file name to clipboardExpand all lines: node.gyp
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -563,6 +563,7 @@
563563
'src/js_native_api_v8_internals.h',
564564
'src/js_stream.cc',
565565
'src/json_utils.cc',
566+
'src/js_udp_wrap.cc',
566567
'src/module_wrap.cc',
567568
'src/node.cc',
568569
'src/node_api.cc',
Collapse file

‎src/async_wrap.h‎

Copy file name to clipboardExpand all lines: src/async_wrap.h
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ namespace node {
5151
V(HTTPINCOMINGMESSAGE) \
5252
V(HTTPCLIENTREQUEST) \
5353
V(JSSTREAM) \
54+
V(JSUDPWRAP) \
5455
V(MESSAGEPORT) \
5556
V(PIPECONNECTWRAP) \
5657
V(PIPESERVERWRAP) \
Collapse file

‎src/js_udp_wrap.cc‎

Copy file name to clipboard
+218Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
#include "udp_wrap.h"
2+
#include "async_wrap-inl.h"
3+
#include "node_errors.h"
4+
#include "node_sockaddr-inl.h"
5+
6+
#include <algorithm>
7+
8+
namespace node {
9+
10+
using errors::TryCatchScope;
11+
using v8::Array;
12+
using v8::Context;
13+
using v8::FunctionCallbackInfo;
14+
using v8::FunctionTemplate;
15+
using v8::HandleScope;
16+
using v8::Int32;
17+
using v8::Local;
18+
using v8::Object;
19+
using v8::String;
20+
using v8::Value;
21+
22+
// JSUDPWrap is a testing utility used by test/common/udppair.js
23+
// to simulate UDP traffic deterministically in Node.js tests.
24+
class JSUDPWrap final : public UDPWrapBase, public AsyncWrap {
25+
public:
26+
JSUDPWrap(Environment* env, Local<Object> obj);
27+
28+
int RecvStart() override;
29+
int RecvStop() override;
30+
ssize_t Send(uv_buf_t* bufs,
31+
size_t nbufs,
32+
const sockaddr* addr) override;
33+
SocketAddress GetPeerName() override;
34+
SocketAddress GetSockName() override;
35+
AsyncWrap* GetAsyncWrap() override { return this; }
36+
37+
static void New(const FunctionCallbackInfo<Value>& args);
38+
static void EmitReceived(const FunctionCallbackInfo<Value>& args);
39+
static void OnSendDone(const FunctionCallbackInfo<Value>& args);
40+
static void OnAfterBind(const FunctionCallbackInfo<Value>& args);
41+
42+
static void Initialize(Local<Object> target,
43+
Local<Value> unused,
44+
Local<Context> context,
45+
void* priv);
46+
SET_NO_MEMORY_INFO()
47+
SET_MEMORY_INFO_NAME(JSUDPWrap)
48+
SET_SELF_SIZE(JSUDPWrap)
49+
};
50+
51+
JSUDPWrap::JSUDPWrap(Environment* env, Local<Object> obj)
52+
: AsyncWrap(env, obj, PROVIDER_JSUDPWRAP) {
53+
MakeWeak();
54+
55+
obj->SetAlignedPointerInInternalField(
56+
kUDPWrapBaseField, static_cast<UDPWrapBase*>(this));
57+
}
58+
59+
int JSUDPWrap::RecvStart() {
60+
HandleScope scope(env()->isolate());
61+
Context::Scope context_scope(env()->context());
62+
TryCatchScope try_catch(env());
63+
Local<Value> value;
64+
int32_t value_int = UV_EPROTO;
65+
if (!MakeCallback(env()->onreadstart_string(), 0, nullptr).ToLocal(&value) ||
66+
!value->Int32Value(env()->context()).To(&value_int)) {
67+
if (try_catch.HasCaught() && !try_catch.HasTerminated())
68+
errors::TriggerUncaughtException(env()->isolate(), try_catch);
69+
}
70+
return value_int;
71+
}
72+
73+
int JSUDPWrap::RecvStop() {
74+
HandleScope scope(env()->isolate());
75+
Context::Scope context_scope(env()->context());
76+
TryCatchScope try_catch(env());
77+
Local<Value> value;
78+
int32_t value_int = UV_EPROTO;
79+
if (!MakeCallback(env()->onreadstop_string(), 0, nullptr).ToLocal(&value) ||
80+
!value->Int32Value(env()->context()).To(&value_int)) {
81+
if (try_catch.HasCaught() && !try_catch.HasTerminated())
82+
errors::TriggerUncaughtException(env()->isolate(), try_catch);
83+
}
84+
return value_int;
85+
}
86+
87+
ssize_t JSUDPWrap::Send(uv_buf_t* bufs,
88+
size_t nbufs,
89+
const sockaddr* addr) {
90+
HandleScope scope(env()->isolate());
91+
Context::Scope context_scope(env()->context());
92+
TryCatchScope try_catch(env());
93+
Local<Value> value;
94+
int64_t value_int = UV_EPROTO;
95+
size_t total_len = 0;
96+
97+
MaybeStackBuffer<Local<Value>, 16> buffers(nbufs);
98+
for (size_t i = 0; i < nbufs; i++) {
99+
buffers[i] = Buffer::Copy(env(), bufs[i].base, bufs[i].len)
100+
.ToLocalChecked();
101+
total_len += bufs[i].len;
102+
}
103+
104+
Local<Value> args[] = {
105+
listener()->CreateSendWrap(total_len)->object(),
106+
Array::New(env()->isolate(), buffers.out(), nbufs),
107+
AddressToJS(env(), addr)
108+
};
109+
110+
if (!MakeCallback(env()->onwrite_string(), arraysize(args), args)
111+
.ToLocal(&value) ||
112+
!value->IntegerValue(env()->context()).To(&value_int)) {
113+
if (try_catch.HasCaught() && !try_catch.HasTerminated())
114+
errors::TriggerUncaughtException(env()->isolate(), try_catch);
115+
}
116+
return value_int;
117+
}
118+
119+
SocketAddress JSUDPWrap::GetPeerName() {
120+
SocketAddress ret;
121+
CHECK(SocketAddress::New(AF_INET, "127.0.0.1", 1337, &ret));
122+
return ret;
123+
}
124+
125+
SocketAddress JSUDPWrap::GetSockName() {
126+
SocketAddress ret;
127+
CHECK(SocketAddress::New(AF_INET, "127.0.0.1", 1337, &ret));
128+
return ret;
129+
}
130+
131+
void JSUDPWrap::New(const FunctionCallbackInfo<Value>& args) {
132+
Environment* env = Environment::GetCurrent(args);
133+
CHECK(args.IsConstructCall());
134+
new JSUDPWrap(env, args.Holder());
135+
}
136+
137+
void JSUDPWrap::EmitReceived(const FunctionCallbackInfo<Value>& args) {
138+
JSUDPWrap* wrap;
139+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
140+
Environment* env = wrap->env();
141+
142+
ArrayBufferViewContents<char> buffer(args[0]);
143+
const char* data = buffer.data();
144+
int len = buffer.length();
145+
146+
CHECK(args[1]->IsInt32()); // family
147+
CHECK(args[2]->IsString()); // address
148+
CHECK(args[3]->IsInt32()); // port
149+
CHECK(args[4]->IsInt32()); // flags
150+
int family = args[1].As<Int32>()->Value() == 4 ? AF_INET : AF_INET6;
151+
Utf8Value address(env->isolate(), args[2]);
152+
int port = args[3].As<Int32>()->Value();
153+
int flags = args[3].As<Int32>()->Value();
154+
155+
sockaddr_storage addr;
156+
CHECK_EQ(sockaddr_for_family(family, *address, port, &addr), 0);
157+
158+
// Repeatedly ask the stream's owner for memory, copy the data that we
159+
// just read from JS into those buffers and emit them as reads.
160+
while (len != 0) {
161+
uv_buf_t buf = wrap->listener()->OnAlloc(len);
162+
ssize_t avail = std::min<size_t>(buf.len, len);
163+
memcpy(buf.base, data, avail);
164+
data += avail;
165+
len -= avail;
166+
wrap->listener()->OnRecv(
167+
avail, buf, reinterpret_cast<sockaddr*>(&addr), flags);
168+
}
169+
}
170+
171+
void JSUDPWrap::OnSendDone(const FunctionCallbackInfo<Value>& args) {
172+
JSUDPWrap* wrap;
173+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
174+
175+
CHECK(args[0]->IsObject());
176+
CHECK(args[1]->IsInt32());
177+
ReqWrap<uv_udp_send_t>* req_wrap;
178+
ASSIGN_OR_RETURN_UNWRAP(&req_wrap, args[0].As<Object>());
179+
int status = args[1].As<Int32>()->Value();
180+
181+
wrap->listener()->OnSendDone(req_wrap, status);
182+
}
183+
184+
void JSUDPWrap::OnAfterBind(const FunctionCallbackInfo<Value>& args) {
185+
JSUDPWrap* wrap;
186+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
187+
188+
wrap->listener()->OnAfterBind();
189+
}
190+
191+
void JSUDPWrap::Initialize(Local<Object> target,
192+
Local<Value> unused,
193+
Local<Context> context,
194+
void* priv) {
195+
Environment* env = Environment::GetCurrent(context);
196+
197+
Local<FunctionTemplate> t = env->NewFunctionTemplate(New);
198+
Local<String> js_udp_wrap_string =
199+
FIXED_ONE_BYTE_STRING(env->isolate(), "JSUDPWrap");
200+
t->SetClassName(js_udp_wrap_string);
201+
t->InstanceTemplate()
202+
->SetInternalFieldCount(UDPWrapBase::kUDPWrapBaseField + 1);
203+
t->Inherit(AsyncWrap::GetConstructorTemplate(env));
204+
205+
UDPWrapBase::AddMethods(env, t);
206+
env->SetProtoMethod(t, "emitReceived", EmitReceived);
207+
env->SetProtoMethod(t, "onSendDone", OnSendDone);
208+
env->SetProtoMethod(t, "onAfterBind", OnAfterBind);
209+
210+
target->Set(env->context(),
211+
js_udp_wrap_string,
212+
t->GetFunction(context).ToLocalChecked()).Check();
213+
}
214+
215+
216+
} // namespace node
217+
218+
NODE_MODULE_CONTEXT_AWARE_INTERNAL(js_udp_wrap, node::JSUDPWrap::Initialize)
Collapse file

‎src/node_binding.cc‎

Copy file name to clipboardExpand all lines: src/node_binding.cc
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
V(http_parser_llhttp) \
5353
V(inspector) \
5454
V(js_stream) \
55+
V(js_udp_wrap) \
5556
V(messaging) \
5657
V(module_wrap) \
5758
V(native_module) \
Collapse file

‎src/udp_wrap.h‎

Copy file name to clipboardExpand all lines: src/udp_wrap.h
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -215,6 +215,11 @@ class UDPWrap final : public HandleWrap,
215215
v8::Local<v8::Object> current_send_req_wrap_;
216216
};
217217

218+
int sockaddr_for_family(int address_family,
219+
const char* address,
220+
const unsigned short port,
221+
sockaddr_storage* addr);
222+
218223
} // namespace node
219224

220225
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
Collapse file

‎test/common/README.md‎

Copy file name to clipboardExpand all lines: test/common/README.md
+13Lines changed: 13 additions & 0 deletions
  • Display the source diff
  • Display the rich diff
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,19 @@ The realpath of the testing temporary directory.
929929

930930
Deletes and recreates the testing temporary directory.
931931

932+
## UDP pair helper
933+
934+
The `common/udppair` module exports a function `makeUDPPair` and a class
935+
`FakeUDPWrap`.
936+
937+
`FakeUDPWrap` emits `'send'` events when data is to be sent on it, and provides
938+
an `emitReceived()` API for actin as if data has been received on it.
939+
940+
`makeUDPPair` returns an object `{ clientSide, serverSide }` where each side
941+
is an `FakeUDPWrap` connected to the other side.
942+
943+
There is no difference between cient or server side beyond their names.
944+
932945
## WPT Module
933946

934947
### `harness`
Collapse file

‎test/common/udppair.js‎

Copy file name to clipboard
+102Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/* eslint-disable node-core/require-common-first, node-core/required-modules */
2+
'use strict';
3+
const { internalBinding } = require('internal/test/binding');
4+
const { JSUDPWrap } = internalBinding('js_udp_wrap');
5+
const EventEmitter = require('events');
6+
7+
// FakeUDPWrap is a testing utility that emulates a UDP connection
8+
// for the sake of making UDP tests more deterministic.
9+
class FakeUDPWrap extends EventEmitter {
10+
constructor() {
11+
super();
12+
13+
this._handle = new JSUDPWrap();
14+
15+
this._handle.onreadstart = () => this._startReading();
16+
this._handle.onreadstop = () => this._stopReading();
17+
this._handle.onwrite =
18+
(wrap, buffers, addr) => this._write(wrap, buffers, addr);
19+
this._handle.getsockname = (obj) => {
20+
Object.assign(obj, { address: '127.0.0.1', family: 'IPv4', port: 1337 });
21+
return 0;
22+
};
23+
24+
this.reading = false;
25+
this.bufferedReceived = [];
26+
this.emitBufferedImmediate = null;
27+
}
28+
29+
_emitBuffered = () => {
30+
if (!this.reading) return;
31+
if (this.bufferedReceived.length > 0) {
32+
this.emitReceived(this.bufferedReceived.shift());
33+
this.emitBufferedImmediate = setImmediate(this._emitBuffered);
34+
} else {
35+
this.emit('wantRead');
36+
}
37+
};
38+
39+
_startReading() {
40+
this.reading = true;
41+
this.emitBufferedImmediate = setImmediate(this._emitBuffered);
42+
}
43+
44+
_stopReading() {
45+
this.reading = false;
46+
clearImmediate(this.emitBufferedImmediate);
47+
}
48+
49+
_write(wrap, buffers, addr) {
50+
this.emit('send', { buffers, addr });
51+
setImmediate(() => this._handle.onSendDone(wrap, 0));
52+
}
53+
54+
afterBind() {
55+
this._handle.onAfterBind();
56+
}
57+
58+
emitReceived(info) {
59+
if (!this.reading) {
60+
this.bufferedReceived.push(info);
61+
return;
62+
}
63+
64+
const {
65+
buffers,
66+
addr: {
67+
family = 4,
68+
address = '127.0.0.1',
69+
port = 1337,
70+
},
71+
flags = 0
72+
} = info;
73+
74+
let familyInt;
75+
switch (family) {
76+
case 'IPv4': familyInt = 4; break;
77+
case 'IPv6': familyInt = 6; break;
78+
default: throw new Error('bad family');
79+
}
80+
81+
for (const buffer of buffers) {
82+
this._handle.emitReceived(buffer, familyInt, address, port, flags);
83+
}
84+
}
85+
}
86+
87+
function makeUDPPair() {
88+
const serverSide = new FakeUDPWrap();
89+
const clientSide = new FakeUDPWrap();
90+
91+
serverSide.on('send',
92+
(chk) => setImmediate(() => clientSide.emitReceived(chk)));
93+
clientSide.on('send',
94+
(chk) => setImmediate(() => serverSide.emitReceived(chk)));
95+
96+
return { serverSide, clientSide };
97+
}
98+
99+
module.exports = {
100+
FakeUDPWrap,
101+
makeUDPPair
102+
};
Collapse file

‎test/sequential/test-async-wrap-getasyncid.js‎

Copy file name to clipboardExpand all lines: test/sequential/test-async-wrap-getasyncid.js
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const { getSystemErrorName } = require('util');
4545
delete providers.STREAMPIPE;
4646
delete providers.MESSAGEPORT;
4747
delete providers.WORKER;
48+
delete providers.JSUDPWRAP;
4849
if (!common.isMainThread)
4950
delete providers.INSPECTORJSBINDING;
5051
delete providers.KEYPAIRGENREQUEST;

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.