Compare commits

...

3 Commits

Author SHA1 Message Date
Don Isaac
b7ef607602 wip 2025-02-18 12:27:37 -08:00
Don Isaac
dbd3a6dc96 more progress i think 2025-02-17 19:26:54 -08:00
Don Isaac
b4eb9ed3ff feat(node/net): add SocketAddress (in C++) 2025-02-17 17:59:34 -08:00
22 changed files with 1067 additions and 46 deletions

View File

@@ -21,6 +21,9 @@ const Async = bun.Async;
const uv = bun.windows.libuv;
const H2FrameParser = @import("./h2_frame_parser.zig").H2FrameParser;
const NodePath = @import("../../node/path.zig");
pub const JSSocketAddress = @import("socket/socket_address.zig").JSSocketAddress;
noinline fn getSSLException(globalThis: *JSC.JSGlobalObject, defaultMessage: []const u8) JSValue {
var zig_str: ZigString = ZigString.init("");
var output_buf: [4096]u8 = undefined;

View File

@@ -0,0 +1,10 @@
const bun = @import("root").bun;
const JSC = bun.JSC;
extern "c" fn JSSocketAddress__getConstructor(*JSC.JSGlobalObject) JSC.JSValue;
extern "c" fn JSSocketAddress__create(*JSC.JSGlobalObject, *JSC.JSString, c_int, bool) *JSC.JSObject;
pub const JSSocketAddress = opaque {
pub const create = JSSocketAddress__create;
pub const getConstructor = JSSocketAddress__getConstructor;
};

View File

@@ -1,63 +1,265 @@
#include "JSSocketAddress.h"
#include "ZigGlobalObject.h"
#include "BunClientData.h"
#include "JavaScriptCore/JSCast.h"
#include "JavaScriptCore/JSObjectInlines.h"
#include "JavaScriptCore/ObjectConstructor.h"
#include "JavaScriptCore/JSCast.h"
#include "ZigGlobalObject.h"
#include "JavaScriptCore/JSCell.h"
#include "ErrorCode.h"
#include "JSSocketAddress.h"
#include "JSSocketAddressConstructor.h"
#include "JSSocketAddressPrototype.h"
using namespace JSC;
namespace Bun {
namespace JSSocketAddress {
// Using a structure with inlined offsets should be more lightweight than a class.
Structure* createStructure(VM& vm, JSGlobalObject* globalObject)
static constexpr PropertyOffset addressOffset = 0;
static constexpr PropertyOffset addressFamilyOffset = 1;
static constexpr PropertyOffset portOffset = 2;
static constexpr PropertyOffset flowLabelOffset = 3;
inline JSC::JSString* JSSocketAddress::address() const
{
JSC::Structure* structure = globalObject->structureCache().emptyObjectStructureForPrototype(
auto value = this->getDirect(addressOffset);
JSC::JSString* str = jsCast<JSC::JSString*>(value);
return str;
// return value.getString(globalObject());
}
inline uint8_t JSSocketAddress::addressFamily() const
{
uint32_t af = this->getDirect(addressFamilyOffset).asUInt32();
ASSERT(af == AF_INET6 || af == AF_INET);
return af;
}
inline in_port_t JSSocketAddress::port() const
{
auto port = this->getDirect(portOffset).asUInt32();
ASSERT(port <= 0xFFFF);
return port;
}
inline uint32_t JSSocketAddress::flowLabel() const
{
return this->getDirect(flowLabelOffset).asUInt32();
}
// =============================================================================
JSSocketAddress* JSSocketAddress::create(JSC::VM& vm,
JSC::JSGlobalObject* globalObject,
JSC::Structure* structure,
JSC::JSString* address,
uint32_t port,
bool isIPv6)
{
return create(vm, globalObject, structure, address, port, isIPv6 ? AF_INET6 : AF_INET, 0);
}
JSSocketAddress* JSSocketAddress::create(JSC::VM& vm,
JSC::JSGlobalObject* globalObject,
JSC::Structure* structure,
JSC::JSString* address,
uint32_t port,
uint8_t addressFamily, // AF_INET | AF_INET6
uint32_t flowLabel)
{
static const NeverDestroyed<String> IPv4 = MAKE_STATIC_STRING_IMPL("IPv4");
static const NeverDestroyed<String> IPv6 = MAKE_STATIC_STRING_IMPL("IPv6");
auto scope = DECLARE_THROW_SCOPE(vm);
address_t addr;
const char* address_bytes = address->value(globalObject)->ascii().data();
switch (inet_pton(addressFamily, address_bytes, &addr)) {
case 1: // ok
break;
case 0: // invalid address
// node throws ERR_INVALID_ADDRESS
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_IP_ADDRESS, "Invalid address"_s);
return nullptr;
case -1: // syserr
// TODO: how to handle system errors?
Bun::throwError(globalObject, scope, ErrorCode::ERR_INVALID_IP_ADDRESS, "Invalid address"_s);
return nullptr;
default:
__builtin_unreachable();
}
auto* af_str = jsString(vm, addressFamily == AF_INET6 ? IPv6 : IPv4);
JSSocketAddress* ptr = new (NotNull, JSC::allocateCell<JSSocketAddress>(vm)) JSSocketAddress(vm, structure);
ptr->m_address = addr;
ptr->finishCreation(vm);
ptr->putDirectOffset(vm, addressOffset, address);
ptr->putDirectOffset(vm, addressFamilyOffset, af_str);
ptr->putDirectOffset(vm, portOffset, jsNumber(static_cast<uint32_t>(port)));
ptr->putDirectOffset(vm, flowLabelOffset, jsNumber(static_cast<uint32_t>(flowLabel)));
return ptr;
}
void JSSocketAddress::destroy(JSC::JSCell* cell)
{
auto* thisObject = jsCast<JSSocketAddress*>(cell);
thisObject->~JSSocketAddress();
}
JSC::GCClient::IsoSubspace* JSSocketAddress::subspaceForImpl(JSC::VM& vm)
{
return WebCore::subspaceForImpl<JSSocketAddress, WebCore::UseCustomHeapCellType::No>(
vm,
[](auto& spaces) { return spaces.m_clientSubspaceForJSSocketAddress.get(); },
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForJSSocketAddress = std::forward<decltype(space)>(space); },
[](auto& spaces) { return spaces.m_subspaceForJSSocketAddress.get(); },
[](auto& spaces, auto&& space) { spaces.m_subspaceForJSSocketAddress = std::forward<decltype(space)>(space); });
}
JSC::JSObject* JSSocketAddress::createPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
{
auto* structure = JSSocketAddressPrototype::createStructure(vm, globalObject, globalObject->objectPrototype());
structure->setMayBePrototype(true);
return JSSocketAddressPrototype::create(vm, globalObject, structure);
}
JSC::Structure* JSSocketAddress::createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
auto* structure = JSC::Structure::create(vm,
globalObject,
globalObject->objectPrototype(),
3);
prototype,
JSC::TypeInfo(JSC::ObjectType, StructureFlags),
info(),
NonArray,
4);
JSC::PropertyOffset offset;
// TODO: add identifiers to CommonIdentifiers?
structure = structure->addPropertyTransition(
vm,
structure,
JSC::Identifier::fromString(vm, "address"_s),
0,
static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete),
offset);
ASSERT(offset == addressOffset);
structure = structure->addPropertyTransition(
vm,
structure,
JSC::Identifier::fromString(vm, "family"_s),
0,
static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete),
offset);
ASSERT(offset == addressFamilyOffset);
structure = structure->addPropertyTransition(
vm,
structure,
JSC::Identifier::fromString(vm, "port"_s),
0,
static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete),
offset);
ASSERT(offset == portOffset);
structure = structure->addPropertyTransition(
vm,
structure,
JSC::Identifier::fromString(vm, "flowlabel"_s),
static_cast<unsigned>(PropertyAttribute::ReadOnly | PropertyAttribute::DontDelete | PropertyAttribute::DontEnum),
offset);
ASSERT(offset == flowLabelOffset);
return structure;
}
} // namespace JSSocketAddress
JSSocketAddress::~JSSocketAddress()
{
}
void JSSocketAddress::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
// initializeProperties(vm, globalObject, prototype);
// TODO: idk how to get a globalobject here
// this->m_address.initLater([](const LazyProperty<JSSocketAddress, address_t>::Initializer& init) {
// auto af = init->owner->addressFamily();
// auto address = init->owner->address();
// address.value()
// address.value(init->vm.)
// });
// ASSERT(inherits(info()));
// reifyStaticProperties(vm, JSSocketAddress::info(),
// JSSocketAddressPrototypeTableValues, *this);
// JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
}
// void JSSocketAddress::initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSObject* prototype)
const ClassInfo JSSocketAddress::s_info
= {
"SocketAddress"_s,
&Base::s_info,
nullptr,
nullptr,
CREATE_METHOD_TABLE(JSSocketAddress)
};
template<typename Visitor>
void JSSocketAddress::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSSocketAddress* thisObject = jsCast<JSSocketAddress*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
thisObject->visitAdditionalChildren<Visitor>(visitor);
}
DEFINE_VISIT_CHILDREN(JSSocketAddress);
template<typename Visitor>
void JSSocketAddress::visitAdditionalChildren(Visitor& visitor)
{
JSSocketAddress* thisObject = this;
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
// TODO: do properties added via putDirectOffset need visiting?
// visitor.append(thisObject->m_address);
}
DEFINE_VISIT_ADDITIONAL_CHILDREN(JSSocketAddress);
template<typename Visitor>
void JSSocketAddress::visitOutputConstraintsImpl(JSCell* cell, Visitor& visitor)
{
auto* thisObject = jsCast<JSSocketAddress*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitOutputConstraints(thisObject, visitor);
thisObject->visitAdditionalChildren(visitor);
}
DEFINE_VISIT_OUTPUT_CONSTRAINTS(JSSocketAddress);
} // namespace Bun
extern "C" JSObject* JSSocketAddress__create(JSGlobalObject* globalObject, JSString* value, int32_t port, bool isIPv6)
{
static const NeverDestroyed<String> IPv4 = MAKE_STATIC_STRING_IMPL("IPv4");
static const NeverDestroyed<String> IPv6 = MAKE_STATIC_STRING_IMPL("IPv6");
VM& vm = globalObject->vm();
auto* global = jsCast<Zig::GlobalObject*>(globalObject);
auto scope = DECLARE_THROW_SCOPE(globalObject->vm());
JSObject* thisObject = constructEmptyObject(vm, global->JSSocketAddressStructure());
thisObject->putDirectOffset(vm, 0, value);
thisObject->putDirectOffset(vm, 1, isIPv6 ? jsString(vm, IPv6) : jsString(vm, IPv4));
thisObject->putDirectOffset(vm, 2, jsNumber(port));
if (UNLIKELY(port < 0 || port > std::numeric_limits<in_port_t>::max())) {
throwRangeError(global, scope, "Port out of range"_s);
return nullptr;
}
return thisObject;
return Bun::JSSocketAddress::create(globalObject->vm(),
globalObject,
global->JSSocketAddressStructure(),
value,
port,
isIPv6 ? AF_INET6 : AF_INET,
0);
}
extern "C" JSC__JSValue JSSocketAddress__getConstructor(JSGlobalObject* globalObject)
{
auto* global = jsCast<Zig::GlobalObject*>(globalObject);
return JSC::JSValue::encode(global->JSSocketAddress());
}

View File

@@ -1,16 +1,131 @@
// The object returned by Bun.serve's .requestIP()
#pragma once
#include "helpers.h"
#include "root.h"
#include "JavaScriptCore/JSObjectInlines.h"
extern "C" {
#if OS(WINDOWS)
#include <WinSock2.h> // in_addr - https://learn.microsoft.com/en-us/windows/win32/api/winsock2/
#include <in6addr.h> // in6_addr - https://learn.microsoft.com/en-us/windows/win32/api/ws2def/
#include <ws2tcpip.h> // inet_ntop, inet_pton - https://learn.microsoft.com/en-us/windows/win32/api/ws2tcpip/
#include <Ws2def.h> // AF_INET, AF_INET6
typedef union address {
struct in_addr ipv4;
struct in6_addr ipv6;
} address_t;
#define in_port_t USHORT
#else
#include <netinet/in.h> // in_addr, in6_addr
#include <arpa/inet.h> // inet_pton, inet_ntop
typedef union address {
struct in_addr ipv4;
struct in6_addr ipv6;
} address_t;
#endif
}
using namespace JSC;
namespace Bun {
namespace JSSocketAddress {
Structure* createStructure(VM& vm, JSGlobalObject* globalObject);
/// `SocketAddress` is written in Zig
// struct SocketAddress;
// class JSSocketAddress : public JSC::JSDestructibleObject {
// public:
// using Base = JSC::JSDestructibleObject;
// using DOMWrapped = SocketAddress;
// static J Structure* createStructure(VM& vm, JSGlobalObject* globalObject);
// }; // class JSSocketAddress
class JSSocketAddress final : public JSC::JSObject {
public:
using Base = JSC::JSObject;
static constexpr unsigned StructureFlags = Base::StructureFlags;
// static constexpr JSC::DestructionMode needsDestruction = NeedsDestruction;
/// Native SocketAddress used in/by Zig code.
// SocketAddress* m_sockaddr { nullptr };
// SocketAddress* m_address
// uint8_t m_address[16];
// LazyProperty<JSSocketAddress, address_t> m_address;
address_t m_address;
JSC::JSString* address() const;
uint8_t addressFamily() const;
in_port_t port() const;
uint32_t flowLabel() const;
/// Returns `nullptr` if the address is invalid. A js exception will be thrown.
static JSSocketAddress* create(JSC::VM& vm,
JSC::JSGlobalObject* globalObject,
JSC::Structure* structure,
JSC::JSString* address,
uint32_t port,
bool isIPv6);
/// Returns `nullptr` if the address is invalid. A js exception will be thrown.
static JSSocketAddress* create(JSC::VM& vm,
JSC::JSGlobalObject* globalObject,
JSC::Structure* structure,
JSC::JSString* address,
uint32_t port,
uint8_t addressFamily, // AF_INET | AF_INET6
uint32_t flowLabel);
static void destroy(JSC::JSCell*);
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return subspaceForImpl(vm);
}
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
static JSObject* createPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
// static JSObject* createConstructor(JSC::VM& vm, JSC::JSGlobalObject* globalObject);
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype);
// {
// return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
// }
// void detach()
// {
// this->sockaddr
// }
// static void analyzeHeap(JSCell*, JSC::HeapAnalyzer&);
// static ptrdiff_t offsetOfWrapped() { return OBJECT_OFFSETOF(JSSocketAddress, m_ctx); }
// /**
// * Estimated size of the object from Zig including the JS wrapper.
// */
// static size_t estimatedSize(JSC::JSCell* cell, JSC::VM& vm);
// /**
// * Memory cost of the object from Zig, without necessarily having a JS wrapper alive.
// */
// static size_t memoryCost(void* ptr);
JSSocketAddress(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
~JSSocketAddress();
void finishCreation(JSC::VM&);
DECLARE_EXPORT_INFO;
DECLARE_VISIT_CHILDREN;
template<typename Visitor> void visitAdditionalChildren(Visitor&);
DECLARE_VISIT_OUTPUT_CONSTRAINTS;
}; // class JSSocketAddress
} // namespace JSSocketAddress
} // namespace Bun
extern "C" JSObject* JSSocketAddress__create(JSGlobalObject* globalObject, JSString* value, int port, bool isIPv6);
extern "C" JSC__JSValue JSSocketAddress__getConstructor(JSGlobalObject* globalObject);

View File

@@ -0,0 +1,105 @@
#include "JSSocketAddressConstructor.h"
#include "JSSocketAddress.h"
#include "JavaScriptCore/Lookup.h"
#include "NodeValidator.h"
#include "ZigGlobalObject.h"
using namespace JSC;
namespace Bun {
const ClassInfo JSSocketAddressConstructor::s_info = {
"SocketAddressConstructor"_s,
&Base::s_info,
nullptr,
nullptr,
CREATE_METHOD_TABLE(JSSocketAddressConstructor)
};
// todo
// static const JSSocketAddressConstructorTableValues[] = {
// { "isSocketAddress"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsScketAddressConstructorFunction_isSocketAddress, 1 },
// { "parse"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), JSC::NoIntrinsic, { JSC::HashTableValue::NativeFunctionType, jsScketAddressConstructorFunction_parse, 1 } },
// };
// void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* global, JSSocketAddressPrototype* prototype)
// {
// }
JSSocketAddressConstructor* JSSocketAddressConstructor::create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype)
{
JSSocketAddressConstructor* ptr = new (NotNull, JSC::allocateCell<JSSocketAddressConstructor>(vm)) JSSocketAddressConstructor(vm, structure);
ptr->finishCreation(vm, globalObject, prototype);
return ptr;
}
// new SocketAddress(AF, address, port = 0, flowLabel = 0)
JSC::EncodedJSValue JSSocketAddressConstructor::construct(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame)
{
Zig::GlobalObject* global = reinterpret_cast<Zig::GlobalObject*>(globalObject);
static const NeverDestroyed<String> port_name = MAKE_STATIC_STRING_IMPL("port");
auto& vm = global->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue af_arg = callFrame->argument(0);
JSValue address_arg = callFrame->argument(1);
JSValue port_arg = callFrame->argument(2);
JSValue flowLabel_arg = callFrame->argument(3);
// addressFamily
V::validateUint32(scope, global, af_arg, "addressFamily"_s, jsBoolean(false));
RETURN_IF_EXCEPTION(scope, {});
uint32_t af = af_arg.toUInt32(global);
if (UNLIKELY(af != AF_INET && af != AF_INET6)) {
throwTypeError(global, scope, "Invalid address family"_s);
return encodedJSUndefined();
}
// address
V::validateString(scope, global, address_arg, "address"_s);
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
JSC::JSString* address = jsCast<JSC::JSString*>(address_arg);
// port
uint32_t port = 0;
if (LIKELY(!port_arg.isUndefined())) {
V::validatePort(scope, global, port_arg, jsString(vm, port_name), true);
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
port = port_arg.toUInt32(global);
ASSERT(port <= std::numeric_limits<in_port_t>().max());
// port = static_cast<in_port_t>(port32);
}
// flowLabel
uint32_t flowLabel = 0;
if (UNLIKELY(!flowLabel_arg.isUndefined())) {
V::validateUint32(scope, global, flowLabel_arg, "flowlabel"_s, jsBoolean(false));
RETURN_IF_EXCEPTION(scope, encodedJSUndefined());
flowLabel = flowLabel_arg.toUInt32(global);
}
auto* structure = global->JSSocketAddressStructure();
JSSocketAddress* sockaddr = JSSocketAddress::create(vm, global, structure, address, port, af, flowLabel);
RETURN_IF_EXCEPTION(scope, encodedJSUndefined()); // throws if inet_pton fails
return JSValue::encode(sockaddr);
}
JSC::EncodedJSValue JSSocketAddressConstructor::call(JSC::JSGlobalObject* global, JSC::CallFrame* callFrame)
{
auto scope = DECLARE_THROW_SCOPE(global->vm());
throwTypeError(global, scope, "Cannot construct SocketAddress"_s);
return encodedJSUndefined();
}
JSSocketAddressConstructor::JSSocketAddressConstructor(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure, call, construct)
{
}
// TODO: reifyStaticProperties
void JSSocketAddressConstructor::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* global, JSC::JSObject* prototype)
{
Base::finishCreation(vm, 1, String("SocketAddress"_s), PropertyAdditionMode::WithoutStructureTransition);
ASSERT(inherits(info()));
putDirectWithoutTransition(vm, vm.propertyNames->prototype, prototype, JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly);
}
} // namespace Bun

View File

@@ -0,0 +1,50 @@
#pragma once
#include "root.h"
#include "JSSocketAddressPrototype.h"
namespace Bun {
class JSSocketAddressConstructor final : public JSC::InternalFunction {
public:
using Base = JSC::InternalFunction;
static JSSocketAddressConstructor* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, JSC::JSObject* prototype);
static constexpr unsigned StructureFlags = Base::StructureFlags;
static constexpr JSC::DestructionMode needsDestruction = JSC::DoesNotNeedDestruction;
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSObject* prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::InternalFunctionType, StructureFlags), info());
}
template<typename, JSC::SubspaceAccess mode> static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
return nullptr;
return &vm.internalFunctionSpace();
// TODO: use separate subspace??
// return WebCore::subspaceForImpl<JSSocketAddressConstructor, WebCore::UseCustomHeapCellType::No>(
// vm,
// [](auto& spaces) { return spaces.m_clientSubspaceForBunClassConstructor.get(); },
// [](auto& spaces, auto&& space) { spaces.m_clientSubspaceForBunClassConstructor = std::forward<decltype(space)>(space); },
// [](auto& spaces) { return spaces.m_subspaceForBunClassConstructor.get(); },
// [](auto& spaces, auto&& space) { spaces.m_subspaceForBunClassConstructor = std::forward<decltype(space)>(space); });
}
// void initializeProperties(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSSocketAddressPrototype* prototype);
// Must be defined for each specialization class.
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*);
static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES call(JSC::JSGlobalObject*, JSC::CallFrame*);
DECLARE_EXPORT_INFO;
protected:
JSSocketAddressConstructor(JSC::VM& vm, JSC::Structure* structure);
void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* global, JSC::JSObject* prototype);
// DECLARE_DEFAULT_FINISH_CREATION;
};
} // namespace Bun

View File

@@ -0,0 +1,13 @@
#include "JSSocketAddressPrototype.h"
// const ClassInfo JSX509CertificatePrototype::s_info = { "X509Certificate"_s,
// &Base::s_info, nullptr, nullptr,
// CREATE_METHOD_TABLE(JSX509CertificatePrototype) };
using namespace JSC;
namespace Bun {
const ClassInfo JSSocketAddressPrototype::s_info = { "SocketAddress"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSSocketAddressPrototype) };
} // namespace Bun

View File

@@ -0,0 +1,42 @@
#pragma once
#include "root.h"
namespace Bun {
class JSSocketAddressPrototype final : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static JSSocketAddressPrototype* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSSocketAddressPrototype* ptr = new (NotNull, JSC::allocateCell<JSSocketAddressPrototype>(vm)) JSSocketAddressPrototype(vm, globalObject, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename CellType, JSC::SubspaceAccess>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
STATIC_ASSERT_ISO_SUBSPACE_SHARABLE(JSSocketAddressPrototype, Base);
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
protected:
JSSocketAddressPrototype(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
: Base(vm, structure)
{
}
// void finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject)
// void finishCreation(JSC::VM& vm) { Base::finishCreation(vm); }
DECLARE_DEFAULT_FINISH_CREATION;
};
} // namespace Bun

View File

@@ -254,7 +254,13 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePort, (JSC::JSGlobalObject * globalO
if (allowZero.isUndefined()) allowZero = jsBoolean(true);
auto allowZero_b = allowZero.toBoolean(globalObject);
if (!port.isNumber() && !port.isString()) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
return V::validatePort(scope, globalObject, port, name, allowZero_b);
}
JSC::EncodedJSValue V::validatePort(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue port, JSValue name, bool allowZero)
{
if (!port.isNumber() && !port.isString()) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (port.isString()) {
auto port_str = port.getString(globalObject);
@@ -290,19 +296,19 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_validatePort, (JSC::JSGlobalObject * globalO
return false;
});
if (trimmed.length() == 0) {
return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
}
}
auto port_num = port.toNumber(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (std::isnan(port_num)) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (std::isinf(port_num)) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (std::fmod(port_num, 1.0) != 0) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (port_num < 0) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (port_num > 0xffff) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (port_num == 0 && !allowZero_b) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero_b);
if (std::isnan(port_num)) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (std::isinf(port_num)) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (std::fmod(port_num, 1.0) != 0) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (port_num < 0) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (port_num > 0xffff) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
if (port_num == 0 && !allowZero) return Bun::ERR::SOCKET_BAD_PORT(scope, globalObject, name, port, allowZero);
return JSValue::encode(port);
}

View File

@@ -38,6 +38,7 @@ JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* g
JSC::EncodedJSValue validateArray(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue minLength);
JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, JSValue name, JSValue positive);
JSC::EncodedJSValue validateUint32(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue value, ASCIILiteral name, JSValue positive);
JSC::EncodedJSValue validatePort(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalObject, JSValue port, JSC::JSValue name, bool allowZero);
}

View File

@@ -115,6 +115,8 @@
#include "JSReadableStreamDefaultReader.h"
#include "JSSink.h"
#include "JSSocketAddress.h"
#include "JSSocketAddressConstructor.h"
#include "JSSocketAddressPrototype.h"
#include "JSSQLStatement.h"
#include "JSStringDecoder.h"
#include "JSTextEncoder.h"
@@ -2914,11 +2916,6 @@ void GlobalObject::finishCreation(VM& vm)
init.vm, reinterpret_cast<Zig::GlobalObject*>(init.owner)));
});
m_JSSocketAddressStructure.initLater(
[](const Initializer<Structure>& init) {
init.set(JSSocketAddress::createStructure(init.vm, init.owner));
});
m_errorConstructorPrepareStackTraceInternalValue.initLater(
[](const Initializer<JSFunction>& init) {
init.set(JSFunction::create(init.vm, init.owner, 2, "ErrorPrepareStackTrace"_s, jsFunctionDefaultErrorPrepareStackTrace, ImplementationVisibility::Public));
@@ -3286,6 +3283,20 @@ void GlobalObject::finishCreation(VM& vm)
init.setConstructor(constructor);
});
m_JSSocketAddressClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
auto* prototype = JSSocketAddressPrototype::create(init.vm, init.global, JSSocketAddressPrototype::createStructure(init.vm, init.global, init.global->objectPrototype()));
auto* structure = JSSocketAddress::createStructure(init.vm, init.global, prototype);
auto* constructor = JSSocketAddressConstructor::create(
init.vm,
init.global,
JSSocketAddressConstructor::createStructure(init.vm, init.global, init.global->functionPrototype()),
jsCast<JSObject*>(prototype));
init.setPrototype(prototype);
init.setStructure(structure);
init.setConstructor(constructor);
});
m_JSBufferClassStructure.initLater(
[](LazyClassStructure::Initializer& init) {
auto prototype = WebCore::createBufferPrototype(init.vm, init.global);
@@ -3890,8 +3901,8 @@ void GlobalObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
thisObject->m_JSHTTPSResponseControllerPrototype.visit(visitor);
thisObject->m_JSHTTPSResponseSinkClassStructure.visit(visitor);
thisObject->m_JSNetworkSinkClassStructure.visit(visitor);
thisObject->m_JSSocketAddressClassStructure.visit(visitor);
thisObject->m_JSFetchTaskletChunkedRequestControllerPrototype.visit(visitor);
thisObject->m_JSSocketAddressStructure.visit(visitor);
thisObject->m_JSSQLStatementStructure.visit(visitor);
thisObject->m_V8GlobalInternals.visit(visitor);
thisObject->m_JSStringDecoderClassStructure.visit(visitor);

View File

@@ -216,6 +216,10 @@ public:
JSC::JSValue NetworkSinkPrototype() const { return m_JSNetworkSinkClassStructure.prototypeInitializedOnMainThread(this); }
JSC::JSValue JSReadableNetworkSinkControllerPrototype() const { return m_JSFetchTaskletChunkedRequestControllerPrototype.getInitializedOnMainThread(this); }
JSC::Structure* JSSocketAddressStructure() const { return m_JSSocketAddressClassStructure.getInitializedOnMainThread(this); }
JSC::JSObject* JSSocketAddress() { return m_JSSocketAddressClassStructure.constructorInitializedOnMainThread(this); }
JSC::JSValue JSSocketAddressPrototype() const { return m_JSSocketAddressClassStructure.prototypeInitializedOnMainThread(this); }
JSC::Structure* JSBufferListStructure() const { return m_JSBufferListClassStructure.getInitializedOnMainThread(this); }
JSC::JSObject* JSBufferList() { return m_JSBufferListClassStructure.constructorInitializedOnMainThread(this); }
JSC::JSValue JSBufferListPrototype() const { return m_JSBufferListClassStructure.prototypeInitializedOnMainThread(this); }
@@ -261,8 +265,6 @@ public:
Structure* ImportMetaObjectStructure() const { return m_importMetaObjectStructure.getInitializedOnMainThread(this); }
Structure* AsyncContextFrameStructure() const { return m_asyncBoundFunctionStructure.getInitializedOnMainThread(this); }
Structure* JSSocketAddressStructure() const { return m_JSSocketAddressStructure.getInitializedOnMainThread(this); }
JSWeakMap* vmModuleContextMap() const { return m_vmModuleContextMap.getInitializedOnMainThread(this); }
Structure* NapiExternalStructure() const { return m_NapiExternalStructure.getInitializedOnMainThread(this); }
@@ -531,6 +533,7 @@ public:
LazyClassStructure m_JSHTTPResponseSinkClassStructure;
LazyClassStructure m_JSHTTPSResponseSinkClassStructure;
LazyClassStructure m_JSNetworkSinkClassStructure;
LazyClassStructure m_JSSocketAddressClassStructure;
LazyClassStructure m_JSStringDecoderClassStructure;
LazyClassStructure m_NapiClassStructure;
@@ -575,7 +578,6 @@ public:
LazyProperty<JSGlobalObject, Structure> m_cachedNodeVMGlobalObjectStructure;
LazyProperty<JSGlobalObject, Structure> m_cachedGlobalProxyStructure;
LazyProperty<JSGlobalObject, Structure> m_commonJSModuleObjectStructure;
LazyProperty<JSGlobalObject, Structure> m_JSSocketAddressStructure;
LazyProperty<JSGlobalObject, Structure> m_memoryFootprintStructure;
LazyProperty<JSGlobalObject, JSObject> m_requireFunctionUnbound;
LazyProperty<JSGlobalObject, JSObject> m_requireResolveFunctionUnbound;

View File

@@ -48,7 +48,7 @@ pub const JSObject = extern struct {
///
/// This is roughly equivalent to creating an object with
/// `Object.create(null)` and adding properties to it.
pub fn createNullProto(pojo: anytype, global: *JSGlobalObject) *JSObject {
pub fn createNullProto(global: *JSGlobalObject, pojo: anytype) *JSObject {
return createFromStructWithPrototype(@TypeOf(pojo), pojo, global, true);
}

View File

@@ -61,6 +61,7 @@ public:
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3Bucket;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSS3File;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSX509Certificate;
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSSocketAddress;
#include "ZigGeneratedClasses+DOMClientIsoSubspaces.h"
/* --- bun --- */

View File

@@ -61,6 +61,7 @@ public:
std::unique_ptr<IsoSubspace> m_subspaceForJSS3Bucket;
std::unique_ptr<IsoSubspace> m_subspaceForJSS3File;
std::unique_ptr<IsoSubspace> m_subspaceForJSX509Certificate;
std::unique_ptr<IsoSubspace> m_subspaceForJSSocketAddress;
#include "ZigGeneratedClasses+DOMIsoSubspaces.h"
/*-- BUN --*/

View File

@@ -116,7 +116,7 @@ fn diffLines(
fn diffListToJS(comptime T: type, global: *JSC.JSGlobalObject, diff_list: MyersDiff.DiffList(T)) bun.JSError!JSC.JSValue {
var array = JSC.JSValue.createEmptyArray(global, diff_list.items.len);
for (diff_list.items, 0..) |*line, i| {
array.putIndex(global, @truncate(i), JSC.JSObject.createNullProto(line.*, global).toJS());
array.putIndex(global, @truncate(i), JSC.JSObject.createNullProto(global, line.*).toJS());
}
return array;
}

View File

@@ -5,6 +5,7 @@ const JSC = bun.JSC;
const string = bun.string;
const Output = bun.Output;
const ZigString = JSC.ZigString;
const JSSocketAddress = @import("../api/bun/socket.zig").JSSocketAddress;
//
//
@@ -71,3 +72,11 @@ pub fn setDefaultAutoSelectFamilyAttemptTimeout(global: *JSC.JSGlobalObject) JSC
}
}).setter, 1, .{});
}
pub fn createNodeNetBinding(global: *JSC.JSGlobalObject) JSC.JSValue {
return JSC.JSObject.createNullProto(global, .{
.SocketAddress = JSSocketAddress.getConstructor(global),
.AF_INET = std.posix.AF.INET,
.AF_INET6 = std.posix.AF.INET6,
}).toJS();
}

View File

@@ -27,6 +27,7 @@ using namespace JSC;
macro(abortSteps) \
macro(addAbortAlgorithmToSignal) \
macro(addEventListener) \
macro(address) \
macro(appendFromJS) \
macro(argv) \
macro(assignToStream) \

View File

@@ -1,3 +1,4 @@
const [addServerName, upgradeDuplexToTLS, isNamedPipeSocket] = $zig("socket.zig", "createNodeTLSBinding");
const { SocketAddress, AF_INET, AF_INET6 } = $zig("node_net_binding.zig", "createNodeNetBinding");
export default { addServerName, upgradeDuplexToTLS, isNamedPipeSocket };
export default { addServerName, upgradeDuplexToTLS, isNamedPipeSocket, SocketAddress, AF_INET, AF_INET6 };

View File

@@ -0,0 +1,124 @@
const { SocketAddress: SocketAddressNative, AF_INET, AF_INET6 } = require("../net");
import type { SocketAddressInitOptions } from "node:net";
const { validateObject, validatePort, validateString, validateUint32 } = require("internal/validators");
const kHandle = Symbol("kHandle");
const kInspect = Symbol.for("nodejs.util.inspect.custom");
var _lazyInspect = null;
function lazyInspect() {
return (_lazyInspect ??= require("node:util").inspect);
}
class SocketAddress {
[kHandle]: SocketAddressNative;
/**
* @returns `true` if `value` is a {@link SocketAddress} instance.
*/
static isSocketAddress(value: unknown): value is SocketAddress {
// NOTE: some bun-specific APIs return `SocketAddressNative` instances.
return $isObject(value) && (kHandle in value || value instanceof SocketAddressNative);
}
/**
* Parse an address string with an optional port number.
*
* @param input the address string to parse, e.g. `1.2.3.4:1234` or `[::1]:0`
* @returns a new {@link SocketAddress} instance or `undefined` if the input
* is invalid.
*/
static parse(input: string): SocketAddress | undefined {
validateString(input, "input");
try {
const { hostname: address, port } = new URL(`http://${input}`);
if (address.startsWith("[") && address.endsWith("]")) {
return new SocketAddress({
address: address.slice(1, -1),
// @ts-ignore -- JSValue | 0 casts to number
port: port | 0,
family: "ipv6",
});
}
return new SocketAddress({
address,
// @ts-ignore -- JSValue | 0 casts to number
port: port | 0,
});
} catch {
// node swallows this error, returning undefined for invalid addresses.
}
}
constructor(options?: SocketAddressInitOptions | SocketAddressNative) {
// allow null?
if ($isUndefinedOrNull(options)) {
this[kHandle] = new SocketAddressNative(AF_INET, "127.0.0.1", 0, 0);
} else {
validateObject(options, "options");
let { address, port = 0, flowlabel, family = "ipv4" } = options;
validatePort(port, "options.port");
if (address !== undefined) validateString(address, "options.address");
if (flowlabel !== undefined) validateUint32(flowlabel, "options.flowlabel");
// Bun's native SocketAddress allows `family` to be `AF_INET` or `AF_INET6`,
// but since we're aiming for nodejs compat in node:net this is not allowed.
if (typeof family?.toLowerCase === "function") {
options.family = family = family.toLowerCase();
}
var af: number;
switch (family) {
case "ipv4":
af = AF_INET;
address ??= "127.0.0.1";
break;
case "ipv6":
af = AF_INET6;
address ??= "::";
break;
default:
throw $ERR_INVALID_ARG_VALUE("options.family", options.family);
}
this[kHandle] = new SocketAddressNative(af, address, port | 0, flowlabel | 0);
}
}
get address() {
return this[kHandle].address;
}
get port() {
return $toLength(this[kHandle].port);
}
get family() {
// return this[kHandle].addrfamily === AF_INET ? "ipv4" : "ipv6";
return this[kHandle].family.toLowerCase();
}
get flowlabel() {
console.log(this[kHandle]);
return this[kHandle].flowlabel;
}
[kInspect](depth: number, options: NodeJS.InspectOptions) {
if (depth < 0) return this;
const opts = options.depth == null ? options : { ...options, depth: options.depth - 1 };
// return `SocketAddress { address: '${this.address}', port: ${this.port}, family: '${this.family}' }`;
return `SocketAddress ${lazyInspect(this.toJSON(), opts)}`;
}
// TODO: kInspect
toJSON() {
return {
address: this.address,
port: this.port,
family: this.family,
flowlabel: this.flowlabel,
};
}
}
export default { SocketAddress };

View File

@@ -23,6 +23,7 @@
const { Duplex } = require("node:stream");
const EventEmitter = require("node:events");
const { addServerName, upgradeDuplexToTLS, isNamedPipeSocket } = require("../internal/net");
const { SocketAddress } = require("../internal/net/socket_address");
const { ExceptionWithHostPort } = require("internal/shared");
// IPv4 Segment
@@ -1558,6 +1559,7 @@ export default {
setDefaultAutoSelectFamilyAttemptTimeout: $zig("node_net_binding.zig", "setDefaultAutoSelectFamilyAttemptTimeout"),
BlockList,
SocketAddress,
// https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/net.js#L2456
Stream: Socket,
};

View File

@@ -0,0 +1,322 @@
/**
* @see https://nodejs.org/api/net.html#class-netsocketaddress
*/
import { SocketAddress, SocketAddressInitOptions } from "node:net";
let v4: SocketAddress;
let v6: SocketAddress;
beforeEach(() => {
v4 = new SocketAddress({ family: "ipv4" });
v6 = new SocketAddress({ family: "ipv6" });
});
describe("SocketAddress constructor", () => {
it("is named SocketAddress", () => {
expect(SocketAddress.name).toBe("SocketAddress");
});
it("is newable", () => {
// @ts-expect-error -- types are wrong. default is kEmptyObject.
expect(new SocketAddress()).toBeInstanceOf(SocketAddress);
});
it("is not callable", () => {
// @ts-expect-error -- types are wrong.
expect(() => SocketAddress()).toThrow(TypeError);
});
describe.each([
new SocketAddress(),
new SocketAddress(undefined),
new SocketAddress({}),
new SocketAddress({ family: undefined }),
new SocketAddress({ family: "ipv4" }),
])("new SocketAddress()", address => {
it("creates an ipv4 address", () => {
expect(address.family).toBe("ipv4");
});
it("address is 127.0.0.1", () => {
expect(address.address).toBe("127.0.0.1");
});
it("port is 0", () => {
expect(address.port).toBe(0);
});
it("flowlabel is 0", () => {
expect(address.flowlabel).toBe(0);
});
}); // </new SocketAddress()>
describe("new SocketAddress({ family: 'ipv6' })", () => {
it("creates a new ipv6 any address", () => {
expect(v6).toMatchObject({
address: "::",
port: 0,
family: "ipv6",
flowlabel: 0,
});
});
}); // </new SocketAddress({ family: 'ipv6' })>
it.each([
[
{ family: "ipv4", address: "1.2.3.4", port: 1234, flowlabel: 9 },
{ address: "1.2.3.4", port: 1234, family: "ipv4", flowlabel: 0 },
],
// family gets lowercased
[{ family: "IPv4" }, { address: "127.0.0.1", family: "ipv4", port: 0 }],
[{ family: "IPV6" }, { address: "::", family: "ipv6", port: 0 }],
] as [SocketAddressInitOptions, Partial<SocketAddress>][])(
"new SocketAddress(%o) matches %o",
(options, expected) => {
const address = new SocketAddress(options);
expect(address).toMatchObject(expected);
},
);
// ===========================================================================
// ============================ INVALID ARGUMENTS ============================
// ===========================================================================
it.each([Symbol.for("ipv4"), function ipv4() {}, { family: "ipv4" }, "ipv1", "ip"])(
"given an invalid family, throws ERR_INVALID_ARG_VALUE",
(family: any) => {
expect(() => new SocketAddress({ family })).toThrowWithCode(Error, "ERR_INVALID_ARG_VALUE");
},
);
// ===========================================================================
// ============================= LEAK DETECTION ==============================
// ===========================================================================
it("does not leak memory", () => {
const growthFactor = 3.0; // allowed growth factor for memory usage
const warmup = 1_000; // # of warmup iterations
const iters = 100_000; // # of iterations
const debug = false;
// we want to hit both cached and uncached code paths
const options = [
undefined,
{ family: "ipv6" },
{ family: "ipv4", address: "1.2.3.4", port: 3000 },
{ family: "ipv6", address: "::3", port: 9 },
] as SocketAddressInitOptions[];
// warmup
var sa;
for (let i = 0; i < warmup; i++) {
sa = new SocketAddress(options[i % options.length]);
}
sa = undefined;
Bun.gc(true);
const before = process.memoryUsage();
if (debug) console.log("before", before);
// actual test
for (let i = 0; i < iters; i++) {
sa = new SocketAddress(options[i % 2]);
}
sa = undefined;
Bun.gc(true);
const after = process.memoryUsage();
if (debug) console.log("after", after);
expect(after.rss).toBeLessThanOrEqual(before.rss * growthFactor);
});
}); // </SocketAddress constructor>
describe("SocketAddress.isSocketAddress", () => {
it("is a function that takes 1 argument", () => {
expect(SocketAddress).toHaveProperty("isSocketAddress");
expect(SocketAddress.isSocketAddress).toBeInstanceOf(Function);
expect(SocketAddress.isSocketAddress).toHaveLength(1);
});
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress, "isSocketAddress");
expect(desc).toEqual({
value: expect.any(Function),
writable: true,
enumerable: false,
configurable: true,
});
});
it("returns true for a SocketAddress instance", () => {
expect(SocketAddress.isSocketAddress(v4)).toBeTrue();
expect(SocketAddress.isSocketAddress(v6)).toBeTrue();
});
it("returns false for POJOs that look like a SocketAddress", () => {
const notASocketAddress = {
address: "127.0.0.1",
port: 0,
family: "ipv4",
flowlabel: 0,
};
expect(SocketAddress.isSocketAddress(notASocketAddress)).toBeFalse();
});
it("returns false for faked SocketAddresses", () => {
const fake = Object.create(SocketAddress.prototype);
for (const key of Object.keys(v4)) {
fake[key] = v4[key];
}
expect(fake instanceof SocketAddress).toBeTrue();
expect(SocketAddress.isSocketAddress(fake)).toBeFalse();
});
}); // </SocketAddress.isSocketAddress>
describe("SocketAddress.parse", () => {
it("is a function that takes 1 argument", () => {
expect(SocketAddress).toHaveProperty("parse");
expect(SocketAddress.parse).toBeInstanceOf(Function);
expect(SocketAddress.parse).toHaveLength(1);
});
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress, "parse");
expect(desc).toEqual({
value: expect.any(Function),
writable: true,
enumerable: false,
configurable: true,
});
});
it.each([
["1.2.3.4", { address: "1.2.3.4", port: 0, family: "ipv4" }],
["192.168.257:1", { address: "192.168.1.1", port: 1, family: "ipv4" }],
["256", { address: "0.0.1.0", port: 0, family: "ipv4" }],
["999999999:12", { address: "59.154.201.255", port: 12, family: "ipv4" }],
["0xffffffff", { address: "255.255.255.255", port: 0, family: "ipv4" }],
["0x.0x.0", { address: "0.0.0.0", port: 0, family: "ipv4" }],
["[1:0::]", { address: "1::", port: 0, family: "ipv6" }],
["[1::8]:123", { address: "1::8", port: 123, family: "ipv6" }],
])("(%s) == %o", (input, expected) => {
expect(SocketAddress.parse(input)).toMatchObject(expected);
});
it.each([
"",
"invalid",
"1.2.3.4.5.6",
"0.0.0.9999",
"1.2.3.4:-1",
"1.2.3.4:null",
"1.2.3.4:65536",
"[1:0:::::::]", // line break
])("(%s) == undefined", invalidInput => {
expect(SocketAddress.parse(invalidInput)).toBeUndefined();
});
}); // </SocketAddress.parse>
describe("SocketAddress.prototype.address", () => {
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "address");
expect(desc).toEqual({
get: expect.any(Function),
set: undefined,
enumerable: false,
configurable: true,
});
});
it("is read-only", () => {
const addr = new SocketAddress();
// @ts-expect-error -- ofc it's read-only
expect(() => (addr.address = "1.2.3.4")).toThrow();
});
}); // </SocketAddress.prototype.address>
describe("SocketAddress.prototype.port", () => {
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "port");
expect(desc).toEqual({
get: expect.any(Function),
set: undefined,
enumerable: false,
configurable: true,
});
});
}); // </SocketAddress.prototype.port>
describe("SocketAddress.prototype.family", () => {
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "family");
expect(desc).toEqual({
get: expect.any(Function),
set: undefined,
enumerable: false,
configurable: true,
});
});
}); // </SocketAddress.prototype.family>
describe("SocketAddress.prototype.flowlabel", () => {
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "flowlabel");
expect(desc).toEqual({
get: expect.any(Function),
set: undefined,
enumerable: false,
configurable: true,
});
});
}); // </SocketAddress.prototype.flowlabel>
describe("SocketAddress.prototype.toJSON", () => {
it("is a function that takes 0 arguments", () => {
expect(SocketAddress.prototype).toHaveProperty("toJSON");
expect(SocketAddress.prototype.toJSON).toBeInstanceOf(Function);
expect(SocketAddress.prototype.toJSON).toHaveLength(0);
});
it("has the correct property descriptor", () => {
const desc = Object.getOwnPropertyDescriptor(SocketAddress.prototype, "toJSON");
expect(desc).toEqual({
value: expect.any(Function),
writable: true,
enumerable: false,
configurable: true,
});
});
it("returns an object with address, port, family, and flowlabel", () => {
expect(v4.toJSON()).toEqual({
address: "127.0.0.1",
port: 0,
family: "ipv4",
flowlabel: 0,
});
expect(v6.toJSON()).toEqual({
address: "::",
port: 0,
family: "ipv6",
flowlabel: 0,
});
});
describe("When called on a default SocketAddress", () => {
let address: Record<string, any>;
beforeEach(() => {
address = v4.toJSON();
});
it("SocketAddress.isSocketAddress() returns false", () => {
expect(SocketAddress.isSocketAddress(address)).toBeFalse();
});
it("does not have SocketAddress as its prototype", () => {
expect(Object.getPrototypeOf(address)).not.toBe(SocketAddress.prototype);
expect(address instanceof SocketAddress).toBeFalse();
});
}); // </When called on a default SocketAddress>
}); // </SocketAddress.prototype.toJSON>