-
Notifications
You must be signed in to change notification settings - Fork 29.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
Showing
8 changed files
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
#include "udp_wrap.h" | ||
#include "async_wrap-inl.h" | ||
#include "node_errors.h" | ||
#include "node_sockaddr-inl.h" | ||
|
||
#include <algorithm> | ||
|
||
namespace node { | ||
|
||
using errors::TryCatchScope; | ||
using v8::Array; | ||
using v8::Context; | ||
using v8::FunctionCallbackInfo; | ||
using v8::FunctionTemplate; | ||
using v8::HandleScope; | ||
using v8::Int32; | ||
using v8::Local; | ||
using v8::Object; | ||
using v8::String; | ||
using v8::Value; | ||
|
||
// JSUDPWrap is a testing utility used by test/common/udppair.js | ||
// to simulate UDP traffic deterministically in Node.js tests. | ||
class JSUDPWrap final : public UDPWrapBase, public AsyncWrap { | ||
public: | ||
JSUDPWrap(Environment* env, Local<Object> obj); | ||
|
||
int RecvStart() override; | ||
int RecvStop() override; | ||
ssize_t Send(uv_buf_t* bufs, | ||
size_t nbufs, | ||
const sockaddr* addr) override; | ||
SocketAddress GetPeerName() override; | ||
SocketAddress GetSockName() override; | ||
AsyncWrap* GetAsyncWrap() override { return this; } | ||
|
||
static void New(const FunctionCallbackInfo<Value>& args); | ||
static void EmitReceived(const FunctionCallbackInfo<Value>& args); | ||
static void OnSendDone(const FunctionCallbackInfo<Value>& args); | ||
static void OnAfterBind(const FunctionCallbackInfo<Value>& args); | ||
|
||
static void Initialize(Local<Object> target, | ||
Local<Value> unused, | ||
Local<Context> context, | ||
void* priv); | ||
SET_NO_MEMORY_INFO() | ||
SET_MEMORY_INFO_NAME(JSUDPWrap) | ||
SET_SELF_SIZE(JSUDPWrap) | ||
}; | ||
|
||
JSUDPWrap::JSUDPWrap(Environment* env, Local<Object> obj) | ||
: AsyncWrap(env, obj, PROVIDER_JSUDPWRAP) { | ||
MakeWeak(); | ||
|
||
obj->SetAlignedPointerInInternalField( | ||
kUDPWrapBaseField, static_cast<UDPWrapBase*>(this)); | ||
} | ||
|
||
int JSUDPWrap::RecvStart() { | ||
HandleScope scope(env()->isolate()); | ||
Context::Scope context_scope(env()->context()); | ||
TryCatchScope try_catch(env()); | ||
Local<Value> value; | ||
int32_t value_int = UV_EPROTO; | ||
if (!MakeCallback(env()->onreadstart_string(), 0, nullptr).ToLocal(&value) || | ||
!value->Int32Value(env()->context()).To(&value_int)) { | ||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) | ||
errors::TriggerUncaughtException(env()->isolate(), try_catch); | ||
} | ||
return value_int; | ||
} | ||
|
||
int JSUDPWrap::RecvStop() { | ||
HandleScope scope(env()->isolate()); | ||
Context::Scope context_scope(env()->context()); | ||
TryCatchScope try_catch(env()); | ||
Local<Value> value; | ||
int32_t value_int = UV_EPROTO; | ||
if (!MakeCallback(env()->onreadstop_string(), 0, nullptr).ToLocal(&value) || | ||
!value->Int32Value(env()->context()).To(&value_int)) { | ||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) | ||
errors::TriggerUncaughtException(env()->isolate(), try_catch); | ||
} | ||
return value_int; | ||
} | ||
|
||
ssize_t JSUDPWrap::Send(uv_buf_t* bufs, | ||
size_t nbufs, | ||
const sockaddr* addr) { | ||
HandleScope scope(env()->isolate()); | ||
Context::Scope context_scope(env()->context()); | ||
TryCatchScope try_catch(env()); | ||
Local<Value> value; | ||
int64_t value_int = UV_EPROTO; | ||
size_t total_len = 0; | ||
|
||
MaybeStackBuffer<Local<Value>, 16> buffers(nbufs); | ||
for (size_t i = 0; i < nbufs; i++) { | ||
buffers[i] = Buffer::Copy(env(), bufs[i].base, bufs[i].len) | ||
.ToLocalChecked(); | ||
total_len += bufs[i].len; | ||
} | ||
|
||
Local<Value> args[] = { | ||
listener()->CreateSendWrap(total_len)->object(), | ||
Array::New(env()->isolate(), buffers.out(), nbufs), | ||
AddressToJS(env(), addr) | ||
}; | ||
|
||
if (!MakeCallback(env()->onwrite_string(), arraysize(args), args) | ||
.ToLocal(&value) || | ||
!value->IntegerValue(env()->context()).To(&value_int)) { | ||
if (try_catch.HasCaught() && !try_catch.HasTerminated()) | ||
errors::TriggerUncaughtException(env()->isolate(), try_catch); | ||
} | ||
return value_int; | ||
} | ||
|
||
SocketAddress JSUDPWrap::GetPeerName() { | ||
SocketAddress ret; | ||
CHECK(SocketAddress::New(AF_INET, "127.0.0.1", 1337, &ret)); | ||
return ret; | ||
} | ||
|
||
SocketAddress JSUDPWrap::GetSockName() { | ||
SocketAddress ret; | ||
CHECK(SocketAddress::New(AF_INET, "127.0.0.1", 1337, &ret)); | ||
return ret; | ||
} | ||
|
||
void JSUDPWrap::New(const FunctionCallbackInfo<Value>& args) { | ||
Environment* env = Environment::GetCurrent(args); | ||
CHECK(args.IsConstructCall()); | ||
new JSUDPWrap(env, args.Holder()); | ||
} | ||
|
||
void JSUDPWrap::EmitReceived(const FunctionCallbackInfo<Value>& args) { | ||
JSUDPWrap* wrap; | ||
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); | ||
Environment* env = wrap->env(); | ||
|
||
ArrayBufferViewContents<char> buffer(args[0]); | ||
const char* data = buffer.data(); | ||
int len = buffer.length(); | ||
|
||
CHECK(args[1]->IsInt32()); // family | ||
CHECK(args[2]->IsString()); // address | ||
CHECK(args[3]->IsInt32()); // port | ||
CHECK(args[4]->IsInt32()); // flags | ||
int family = args[1].As<Int32>()->Value() == 4 ? AF_INET : AF_INET6; | ||
Utf8Value address(env->isolate(), args[2]); | ||
int port = args[3].As<Int32>()->Value(); | ||
int flags = args[3].As<Int32>()->Value(); | ||
|
||
sockaddr_storage addr; | ||
CHECK_EQ(sockaddr_for_family(family, *address, port, &addr), 0); | ||
|
||
// Repeatedly ask the stream's owner for memory, copy the data that we | ||
// just read from JS into those buffers and emit them as reads. | ||
while (len != 0) { | ||
uv_buf_t buf = wrap->listener()->OnAlloc(len); | ||
ssize_t avail = std::min<size_t>(buf.len, len); | ||
memcpy(buf.base, data, avail); | ||
data += avail; | ||
len -= avail; | ||
wrap->listener()->OnRecv( | ||
avail, buf, reinterpret_cast<sockaddr*>(&addr), flags); | ||
} | ||
} | ||
|
||
void JSUDPWrap::OnSendDone(const FunctionCallbackInfo<Value>& args) { | ||
JSUDPWrap* wrap; | ||
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); | ||
|
||
CHECK(args[0]->IsObject()); | ||
CHECK(args[1]->IsInt32()); | ||
ReqWrap<uv_udp_send_t>* req_wrap; | ||
ASSIGN_OR_RETURN_UNWRAP(&req_wrap, args[0].As<Object>()); | ||
int status = args[1].As<Int32>()->Value(); | ||
|
||
wrap->listener()->OnSendDone(req_wrap, status); | ||
} | ||
|
||
void JSUDPWrap::OnAfterBind(const FunctionCallbackInfo<Value>& args) { | ||
JSUDPWrap* wrap; | ||
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder()); | ||
|
||
wrap->listener()->OnAfterBind(); | ||
} | ||
|
||
void JSUDPWrap::Initialize(Local<Object> target, | ||
Local<Value> unused, | ||
Local<Context> context, | ||
void* priv) { | ||
Environment* env = Environment::GetCurrent(context); | ||
|
||
Local<FunctionTemplate> t = env->NewFunctionTemplate(New); | ||
Local<String> js_udp_wrap_string = | ||
FIXED_ONE_BYTE_STRING(env->isolate(), "JSUDPWrap"); | ||
t->SetClassName(js_udp_wrap_string); | ||
t->InstanceTemplate() | ||
->SetInternalFieldCount(UDPWrapBase::kUDPWrapBaseField + 1); | ||
t->Inherit(AsyncWrap::GetConstructorTemplate(env)); | ||
|
||
UDPWrapBase::AddMethods(env, t); | ||
env->SetProtoMethod(t, "emitReceived", EmitReceived); | ||
env->SetProtoMethod(t, "onSendDone", OnSendDone); | ||
env->SetProtoMethod(t, "onAfterBind", OnAfterBind); | ||
|
||
target->Set(env->context(), | ||
js_udp_wrap_string, | ||
t->GetFunction(context).ToLocalChecked()).Check(); | ||
} | ||
|
||
|
||
} // namespace node | ||
|
||
NODE_MODULE_CONTEXT_AWARE_INTERNAL(js_udp_wrap, node::JSUDPWrap::Initialize) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
/* eslint-disable node-core/require-common-first, node-core/required-modules */ | ||
'use strict'; | ||
const { internalBinding } = require('internal/test/binding'); | ||
const { JSUDPWrap } = internalBinding('js_udp_wrap'); | ||
const EventEmitter = require('events'); | ||
|
||
// FakeUDPWrap is a testing utility that emulates a UDP connection | ||
// for the sake of making UDP tests more deterministic. | ||
class FakeUDPWrap extends EventEmitter { | ||
constructor() { | ||
super(); | ||
|
||
this._handle = new JSUDPWrap(); | ||
|
||
this._handle.onreadstart = () => this._startReading(); | ||
this._handle.onreadstop = () => this._stopReading(); | ||
this._handle.onwrite = | ||
(wrap, buffers, addr) => this._write(wrap, buffers, addr); | ||
this._handle.getsockname = (obj) => { | ||
Object.assign(obj, { address: '127.0.0.1', family: 'IPv4', port: 1337 }); | ||
return 0; | ||
}; | ||
|
||
this.reading = false; | ||
this.bufferedReceived = []; | ||
this.emitBufferedImmediate = null; | ||
} | ||
|
||
_emitBuffered = () => { | ||
if (!this.reading) return; | ||
if (this.bufferedReceived.length > 0) { | ||
this.emitReceived(this.bufferedReceived.shift()); | ||
this.emitBufferedImmediate = setImmediate(this._emitBuffered); | ||
} else { | ||
this.emit('wantRead'); | ||
} | ||
}; | ||
|
||
_startReading() { | ||
this.reading = true; | ||
this.emitBufferedImmediate = setImmediate(this._emitBuffered); | ||
} | ||
|
||
_stopReading() { | ||
this.reading = false; | ||
clearImmediate(this.emitBufferedImmediate); | ||
} | ||
|
||
_write(wrap, buffers, addr) { | ||
this.emit('send', { buffers, addr }); | ||
setImmediate(() => this._handle.onSendDone(wrap, 0)); | ||
} | ||
|
||
afterBind() { | ||
this._handle.onAfterBind(); | ||
} | ||
|
||
emitReceived(info) { | ||
if (!this.reading) { | ||
this.bufferedReceived.push(info); | ||
return; | ||
} | ||
|
||
const { | ||
buffers, | ||
addr: { | ||
family = 4, | ||
address = '127.0.0.1', | ||
port = 1337, | ||
}, | ||
flags = 0 | ||
} = info; | ||
|
||
let familyInt; | ||
switch (family) { | ||
case 'IPv4': familyInt = 4; break; | ||
case 'IPv6': familyInt = 6; break; | ||
default: throw new Error('bad family'); | ||
} | ||
|
||
for (const buffer of buffers) { | ||
this._handle.emitReceived(buffer, familyInt, address, port, flags); | ||
} | ||
} | ||
} | ||
|
||
function makeUDPPair() { | ||
const serverSide = new FakeUDPWrap(); | ||
const clientSide = new FakeUDPWrap(); | ||
|
||
serverSide.on('send', | ||
(chk) => setImmediate(() => clientSide.emitReceived(chk))); | ||
clientSide.on('send', | ||
(chk) => setImmediate(() => serverSide.emitReceived(chk))); | ||
|
||
return { serverSide, clientSide }; | ||
} | ||
|
||
module.exports = { | ||
FakeUDPWrap, | ||
makeUDPPair | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters