mirror of
https://github.com/oven-sh/bun
synced 2026-02-08 01:49:33 +00:00
Compare commits
3 Commits
ciro/fix-a
...
don/feat/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b7ef607602 | ||
|
|
dbd3a6dc96 | ||
|
|
b4eb9ed3ff |
@@ -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;
|
||||
|
||||
10
src/bun.js/api/bun/socket/socket_address.zig
Normal file
10
src/bun.js/api/bun/socket/socket_address.zig
Normal 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;
|
||||
};
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
105
src/bun.js/bindings/JSSocketAddressConstructor.cpp
Normal file
105
src/bun.js/bindings/JSSocketAddressConstructor.cpp
Normal 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
|
||||
50
src/bun.js/bindings/JSSocketAddressConstructor.h
Normal file
50
src/bun.js/bindings/JSSocketAddressConstructor.h
Normal 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
|
||||
13
src/bun.js/bindings/JSSocketAddressPrototype.cpp
Normal file
13
src/bun.js/bindings/JSSocketAddressPrototype.cpp
Normal 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
|
||||
42
src/bun.js/bindings/JSSocketAddressPrototype.h
Normal file
42
src/bun.js/bindings/JSSocketAddressPrototype.h
Normal 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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 --- */
|
||||
|
||||
|
||||
@@ -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 --*/
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ using namespace JSC;
|
||||
macro(abortSteps) \
|
||||
macro(addAbortAlgorithmToSignal) \
|
||||
macro(addEventListener) \
|
||||
macro(address) \
|
||||
macro(appendFromJS) \
|
||||
macro(argv) \
|
||||
macro(assignToStream) \
|
||||
|
||||
@@ -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 };
|
||||
|
||||
124
src/js/internal/net/socket_address.ts
Normal file
124
src/js/internal/net/socket_address.ts
Normal 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 };
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
322
test/js/node/net/socket-address.spec.ts
Normal file
322
test/js/node/net/socket-address.spec.ts
Normal 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>
|
||||
Reference in New Issue
Block a user