Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions crates/perry-api-manifest/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,25 @@ pub static API_MANIFEST: &[ApiEntry] = &[
method("net", "destroy", true, Some("Socket")),
method("net", "on", true, Some("Socket")),
method("net", "upgradeToTLS", true, Some("Socket")),
// Issue #811 — IP classification helpers + Happy-Eyeballs default
// accessors. Pure string/global-flag functions.
method("net", "isIP", false, None),
method("net", "isIPv4", false, None),
method("net", "isIPv6", false, None),
method("net", "getDefaultAutoSelectFamily", false, None),
method("net", "setDefaultAutoSelectFamily", false, None),
method(
"net",
"getDefaultAutoSelectFamilyAttemptTimeout",
false,
None,
),
method(
"net",
"setDefaultAutoSelectFamilyAttemptTimeout",
false,
None,
),
method_sig(
"tls",
"connect",
Expand Down
65 changes: 65 additions & 0 deletions crates/perry-codegen/src/lower_call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7006,6 +7006,71 @@ const NATIVE_MODULE_TABLE: &[NativeModSig] = &[
args: &[],
ret: NR_PTR,
},
// Issue #810/#811 — IP classification helpers + Happy-Eyeballs default
// accessors. Pure string/global-flag functions, no sockets or I/O.
NativeModSig {
module: "net",
has_receiver: false,
method: "isIP",
class_filter: None,
runtime: "js_net_is_ip",
args: &[NA_STR],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "isIPv4",
class_filter: None,
runtime: "js_net_is_ipv4",
args: &[NA_STR],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "isIPv6",
class_filter: None,
runtime: "js_net_is_ipv6",
args: &[NA_STR],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "getDefaultAutoSelectFamily",
class_filter: None,
runtime: "js_net_get_default_auto_select_family",
args: &[],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "setDefaultAutoSelectFamily",
class_filter: None,
runtime: "js_net_set_default_auto_select_family",
args: &[NA_F64],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "getDefaultAutoSelectFamilyAttemptTimeout",
class_filter: None,
runtime: "js_net_get_default_auto_select_family_attempt_timeout",
args: &[],
ret: NR_F64,
},
NativeModSig {
module: "net",
has_receiver: false,
method: "setDefaultAutoSelectFamilyAttemptTimeout",
class_filter: None,
runtime: "js_net_set_default_auto_select_family_attempt_timeout",
args: &[NA_F64],
ret: NR_F64,
},
// Instance method: `sock.connect(port, host)` initiates the deferred
// TCP connection on a `new net.Socket()`-allocated handle. Twin of
// the `createConnection` factory above — both end up in the same
Expand Down
90 changes: 90 additions & 0 deletions crates/perry-ext-net/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,96 @@ enum PendingNetEvent {

// ─── Helpers ─────────────────────────────────────────────────────────────────

/// Issue #811 — `net.isIP(s)` returns 0/4/6 (number).
fn classify_ip(s: &str) -> i32 {
if is_ipv4_str(s) {
4
} else if is_ipv6_str(s) {
6
} else {
0
}
}

fn is_ipv4_str(s: &str) -> bool {
s.parse::<std::net::Ipv4Addr>().is_ok()
}

fn is_ipv6_str(s: &str) -> bool {
// Node's `net.isIPv6` rejects brackets and zone-id (`%`) suffixes —
// those forms aren't bare addresses.
if s.contains('[') || s.contains(']') || s.contains('%') {
return false;
}
s.parse::<std::net::Ipv6Addr>().is_ok()
}

#[no_mangle]
pub unsafe extern "C" fn js_net_is_ip(s_ptr: i64) -> f64 {
let kind = match string_from_header_i64(s_ptr) {
Some(s) => classify_ip(&s),
None => 0,
};
f64::from_bits(JsValue::from_number(kind as f64).0)
}

#[no_mangle]
pub unsafe extern "C" fn js_net_is_ipv4(s_ptr: i64) -> f64 {
let is = match string_from_header_i64(s_ptr) {
Some(s) => is_ipv4_str(&s),
None => false,
};
f64::from_bits(JsValue::from_bool(is).0)
}

#[no_mangle]
pub unsafe extern "C" fn js_net_is_ipv6(s_ptr: i64) -> f64 {
let is = match string_from_header_i64(s_ptr) {
Some(s) => is_ipv6_str(&s),
None => false,
};
f64::from_bits(JsValue::from_bool(is).0)
}

// Happy-Eyeballs (auto-select-family) defaults. Process-wide globals
// that `getDefault*` reads and `setDefault*` writes.
static AUTO_SELECT_FAMILY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
// Node's current default is 500ms (raised from 250 in v20.x); pin to
// 500 so byte-for-byte parity holds against `node --experimental-strip-types`.
static AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS: std::sync::atomic::AtomicI32 =
std::sync::atomic::AtomicI32::new(500);

#[no_mangle]
pub unsafe extern "C" fn js_net_get_default_auto_select_family() -> f64 {
let v = AUTO_SELECT_FAMILY.load(std::sync::atomic::Ordering::Relaxed);
f64::from_bits(JsValue::from_bool(v).0)
}

#[no_mangle]
pub unsafe extern "C" fn js_net_set_default_auto_select_family(val_f64: f64) -> f64 {
let val = JsValue(val_f64.to_bits()).to_bool();
AUTO_SELECT_FAMILY.store(val, std::sync::atomic::Ordering::Relaxed);
f64::from_bits(JsValue::UNDEFINED.0)
}

#[no_mangle]
pub unsafe extern "C" fn js_net_get_default_auto_select_family_attempt_timeout() -> f64 {
let v = AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS.load(std::sync::atomic::Ordering::Relaxed);
f64::from_bits(JsValue::from_number(v as f64).0)
}

#[no_mangle]
pub unsafe extern "C" fn js_net_set_default_auto_select_family_attempt_timeout(ms_f64: f64) -> f64 {
let n = JsValue(ms_f64.to_bits()).to_number();
let ms = if n.is_finite() && n >= 0.0 {
n as i32
} else {
0
};
AUTO_SELECT_FAMILY_ATTEMPT_TIMEOUT_MS.store(ms, std::sync::atomic::Ordering::Relaxed);
f64::from_bits(JsValue::UNDEFINED.0)
}

unsafe fn string_from_header_i64(ptr: i64) -> Option<String> {
let p = ptr as usize;
if p < 0x1000 {
Expand Down
16 changes: 15 additions & 1 deletion docs/api/perry.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Auto-generated from Perry's API manifest (#465). Do not edit by hand.
// Source: perry-api-manifest::API_MANIFEST
// Coverage: 826 entries across 70 modules
// Coverage: 833 entries across 70 modules

declare module "argon2" {
/** stdlib */
Expand Down Expand Up @@ -447,6 +447,20 @@ declare module "net" {
export function connect(p0: any, p1: any, p2: any): any;
/** stdlib */
export function createConnection(p0: any, p1: any, p2: any): any;
/** stdlib */
export function getDefaultAutoSelectFamily(...args: any[]): any;
/** stdlib */
export function getDefaultAutoSelectFamilyAttemptTimeout(...args: any[]): any;
/** stdlib */
export function isIP(...args: any[]): any;
/** stdlib */
export function isIPv4(...args: any[]): any;
/** stdlib */
export function isIPv6(...args: any[]): any;
/** stdlib */
export function setDefaultAutoSelectFamily(...args: any[]): any;
/** stdlib */
export function setDefaultAutoSelectFamilyAttemptTimeout(...args: any[]): any;
}

declare module "node-cron" {
Expand Down
9 changes: 8 additions & 1 deletion docs/src/api/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

This page is auto-generated from Perry's compile-time API manifest (`perry-api-manifest::API_MANIFEST`). It is the source of truth for what `perry compile` accepts; references to symbols not listed here produce `R005 UnimplementedApi` (issue #463). Stubs (#464) are flagged ⚠ — they link cleanly but no-op at runtime on the chosen target.

Total: 826 entries across 70 modules.
Total: 833 entries across 70 modules.

## Modules

Expand Down Expand Up @@ -704,7 +704,14 @@ Total: 826 entries across 70 modules.
- `createConnection` — module
- `destroy` — instance *(class: `Socket`)*
- `end` — instance *(class: `Socket`)*
- `getDefaultAutoSelectFamily` — module
- `getDefaultAutoSelectFamilyAttemptTimeout` — module
- `isIP` — module
- `isIPv4` — module
- `isIPv6` — module
- `on` — instance *(class: `Socket`)*
- `setDefaultAutoSelectFamily` — module
- `setDefaultAutoSelectFamilyAttemptTimeout` — module
- `upgradeToTLS` — instance *(class: `Socket`)*
- `write` — instance *(class: `Socket`)*

Expand Down
Loading