Skip to content

Commit 8545fb2

Browse 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 <[email protected]>
1 parent 0affe86 commit 8545fb2

File tree

8 files changed

+342
-0
lines changed

8 files changed

+342
-0
lines changed

‎node.gyp‎

Lines 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',

‎src/async_wrap.h‎

Lines 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) \

‎src/js_udp_wrap.cc‎

Lines 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+
namespacenode{
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+
classJSUDPWrapfinal : public UDPWrapBase, public AsyncWrap{
25+
public:
26+
JSUDPWrap(Environment* env, Local<Object> obj);
27+
28+
intRecvStart() override;
29+
intRecvStop() override;
30+
ssize_tSend(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{returnthis}
36+
37+
staticvoidNew(const FunctionCallbackInfo<Value>& args);
38+
staticvoidEmitReceived(const FunctionCallbackInfo<Value>& args);
39+
staticvoidOnSendDone(const FunctionCallbackInfo<Value>& args);
40+
staticvoidOnAfterBind(const FunctionCallbackInfo<Value>& args);
41+
42+
staticvoidInitialize(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+
intJSUDPWrap::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+
intJSUDPWrap::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_tJSUDPWrap::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+
voidJSUDPWrap::New(const FunctionCallbackInfo<Value>& args){
132+
Environment* env = Environment::GetCurrent(args);
133+
CHECK(args.IsConstructCall());
134+
newJSUDPWrap(env, args.Holder());
135+
}
136+
137+
voidJSUDPWrap::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+
constchar* 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+
voidJSUDPWrap::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+
voidJSUDPWrap::OnAfterBind(const FunctionCallbackInfo<Value>& args){
185+
JSUDPWrap* wrap;
186+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
187+
188+
wrap->listener()->OnAfterBind();
189+
}
190+
191+
voidJSUDPWrap::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)

‎src/node_binding.cc‎

Lines 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) \

‎src/udp_wrap.h‎

Lines 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+
intsockaddr_for_family(int address_family,
219+
constchar* address,
220+
constunsignedshort port,
221+
sockaddr_storage* addr);
222+
218223
} // namespace node
219224

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

‎test/common/README.md‎

Lines changed: 13 additions & 0 deletions
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`

‎test/common/udppair.js‎

Lines 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+
constEventEmitter=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+
classFakeUDPWrapextendsEventEmitter{
10+
constructor(){
11+
super();
12+
13+
this._handle=newJSUDPWrap();
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+
return0;
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+
letfamilyInt;
75+
switch(family){
76+
case'IPv4': familyInt=4;break;
77+
case'IPv6': familyInt=6;break;
78+
default: thrownewError('bad family');
79+
}
80+
81+
for(constbufferofbuffers){
82+
this._handle.emitReceived(buffer,familyInt,address,port,flags);
83+
}
84+
}
85+
}
86+
87+
functionmakeUDPPair(){
88+
constserverSide=newFakeUDPWrap();
89+
constclientSide=newFakeUDPWrap();
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+
};

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ const{getSystemErrorName } = require('util');
4545
deleteproviders.STREAMPIPE;
4646
deleteproviders.MESSAGEPORT;
4747
deleteproviders.WORKER;
48+
deleteproviders.JSUDPWRAP;
4849
if(!common.isMainThread)
4950
deleteproviders.INSPECTORJSBINDING;
5051
deleteproviders.KEYPAIRGENREQUEST;

0 commit comments

Comments
(0)