/
nv-lang
/
nova
Обзор
Документация
Войти
/
nv-lang
/
nova
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
main
std/src/net/addr.nv
169 строк
7 KB
Evgeniy Golovin
fix(net): порт унифицирован до int на всей публичной поверхности
09 авг 2026, 15:04
09 авг 2026, 15:04
6369339
Код
Авторство
О чём код?
// SPDX-License-Identifier: MIT OR Apache-2.0 // std/net/addr.nv — SocketAddr value-record. // // SocketAddr is DATA, not a handle: the value owns a 20-byte []u8 image of the // C `NovaNetAddr` POD (bytes[16] + port u16 + family u8 + pad). Copies are cheap // (the image is immutable after construction, so the shared backing is // value-equivalent). This closes [M-net-socketaddr-value-record] for the new // layer — no `_nova_alloc_addr` C heap, no opaque handle, no TLS. // // Construction / parsing / formatting are plain `fn`s (no I/O, no parking, no // capability → not effect operations, per the placement rule): they call the // buffer-based address FFI directly (the C layer owns the struct layout, so the // .nv side never bakes offsets or endianness). DNS `resolve` (host → addresses = // real I/O) stays in the `Net` effect (dns.nv). module std.net /// IPv4/IPv6 socket address (host + port), stored as owned data. /// /// # Examples /// /// ```nova /// let a4 = SocketAddr.loopback(8080) // 127.0.0.1:8080 /// let a6 = SocketAddr.loopback_v6(8080) // [::1]:8080 /// let av = SocketAddr.v4(192, 168, 1, 1, 80) /// let ap = "127.0.0.1:9090".to_socket_addr()!! /// let aw = SocketAddr.any(8080) // 0.0.0.0:8080, ALL interfaces /// ``` #stable(since = "0.1") export type SocketAddr value priv { raw []u8 } // Byte size of the C `NovaNetAddr` POD image the value wraps: // bytes[16] (IPv6, or IPv4 in the first 4) + port u16 + family u8 + pad u8. // The C layer owns this layout; the .nv side treats the image opaquely. // Size is queried from C (прецедент lazy-static глобалов: collate.nv); // при появлении size_of-API станет типизированным описанием. ro ADDR_IMAGE_BYTES int = net_addr_size() // A fresh, zeroed NovaNetAddr image buffer. fn addr_image() -> []u8 => []u8.new().resize(ADDR_IMAGE_BYTES, 0 as u8) // Package-private: wrap an already-filled 20-byte image into a SocketAddr. // Used by the tcp/udp/dns handlers, which fill an out-buffer via the FFI. fn SocketAddr.from_image(b []u8) -> SocketAddr => { raw: b } // Package-private: raw image pointer for the transport FFI (`*()` = NovaNetAddr*). fn SocketAddr @ptr() -> *() => @raw.ptr() as *() /// Create IPv4 loopback address 127.0.0.1:port (port 0 = OS-assigned). /// /// `port` is `int`, not `u16` — a `u16` boundary here would let an /// out-of-range value (e.g. from `env(...).to_int()`) truncate SILENTLY /// (`70000 as u16` == 4464 — the server comes up on the WRONG port with no /// diagnostic). `int` + `requires` fails loudly, at the right place; for a /// literal port the contract is proved statically, zero runtime cost. #stable(since = "0.1") export fn SocketAddr.loopback(port int) -> SocketAddr requires port >= 0 && port <= 0xffff { mut b = addr_image() unsafe { net_addr_loopback_into(port as u16, b.ptr()) } SocketAddr.from_image(b) } /// Create IPv6 loopback address [::1]:port. See [`SocketAddr.loopback`] for /// why `port` is `int`, not `u16`. #stable(since = "0.1") export fn SocketAddr.loopback_v6(port int) -> SocketAddr requires port >= 0 && port <= 0xffff { mut b = addr_image() unsafe { net_addr_loopback_v6_into(port as u16, b.ptr()) } SocketAddr.from_image(b) } /// Create IPv4 address from dotted-quad octets. See [`SocketAddr.loopback`] /// for why `port` is `int`, not `u16`. #stable(since = "0.1") export fn SocketAddr.v4(a u8, b u8, c u8, d u8, port int) -> SocketAddr requires port >= 0 && port <= 0xffff { mut img = addr_image() unsafe { net_addr_v4_into(a, b, c, d, port as u16, img.ptr()) } SocketAddr.from_image(img) } /// Create IPv4 "any interface" address 0.0.0.0:port — binds to ALL local /// interfaces (Go `net.Listen("tcp", ":port")` / nginx convention), **NOT** /// loopback. Use [`SocketAddr.loopback`] for a localhost-only bind (also see /// there for why `port` is `int`, not `u16`). #stable(since = "0.1") export fn SocketAddr.any(port int) -> SocketAddr requires port >= 0 && port <= 0xffff { mut img = addr_image() unsafe { net_addr_v4_into(0, 0, 0, 0, port as u16, img.ptr()) } SocketAddr.from_image(img) } /// Create IPv6 "any interface" address [::]:port — mirrors [`SocketAddr.any`] /// for IPv6; **NOT** loopback (see [`SocketAddr.loopback_v6`]). #stable(since = "0.1") export fn SocketAddr.any_v6(port int) -> SocketAddr requires port >= 0 && port <= 0xffff { mut b = addr_image() unsafe { net_addr_any_v6_into(port as u16, b.ptr()) } SocketAddr.from_image(b) } /// Parse "host:port" into a SocketAddr. /// /// Returns `Err(NetError.InvalidAddr(s))` on a malformed address, /// `Err(NetError.InvalidPort)` on an out-of-range port. IPv6 literals must be /// bracketed: `"[::1]:9090"`. An empty host before the colon (`":8080"`) is /// the Go/nginx **"any interface"** convention — parses to `0.0.0.0:8080`, /// **NOT** loopback (see [`SocketAddr.any`] to build the same value without a /// string). Pure — parses a literal, no DNS / no I/O. Canon `x.to_*()` /// conversion from a `ro`-source (a static-door twin was considered and /// retracted). #stable(since = "0.1") export fn str @to_socket_addr() -> Result[SocketAddr, NetError] { mut b = addr_image() ro code = unsafe { net_addr_parse(@ptr(), @byte_len(), b.ptr()) } if code == 2 { Err(NetError.InvalidPort) } else if code != 0 { Err(NetError.InvalidAddr(@)) } else { Ok(SocketAddr.from_image(b)) } } /// Get the port number. `int`, not `u16` — see [`SocketAddr.loopback`] for why /// the port type is `int` on the whole public surface (the `u16` C image is /// an FFI-boundary detail, widened here on the way out; no truncation risk /// widening `u16` -> `int`). #stable(since = "0.1") export fn SocketAddr @port() -> int => unsafe { net_addr_port(@ptr()) as int } /// Return `true` if this is an IPv4 address. #stable(since = "0.1") export fn SocketAddr @is_v4() -> bool => unsafe { net_addr_is_v4(@ptr()) } /// Return `true` if this is an IPv6 address. #stable(since = "0.1") export fn SocketAddr @is_v6() -> bool => unsafe { net_addr_is_v6(@ptr()) } /// Get the host IP as a human-readable string (v4 dotted-decimal / v6 hex). #stable(since = "0.1") export fn SocketAddr @ip() -> str { mut buf []u8 = []u8.new() buf.resize(64, 0 as u8) ro n = unsafe { net_addr_ip(@ptr(), buf.ptr(), 64) } // [M-174.1-vec-method-chain-elem-erasure]: explicit []u8 local; decode via // the non-unsafe Result-form + `??` fallback (the FFI buffer is ASCII — // Err is unreachable). The unsafe `to_str_unchecked` spelling hit a P67 // annotation gap in cross-package CUs (examples/net) — see marker. ro ip_bytes []u8 = buf[..n] ip_bytes.to_str() ?? "" } /// Return a human-readable "host:port" string (v6: "[host]:port"). #stable(since = "0.1") export fn SocketAddr @to_str() -> str { mut buf []u8 = []u8.new() buf.resize(128, 0 as u8) ro n = unsafe { net_addr_to_str(@ptr(), buf.ptr(), 128) } // [M-174.1-vec-method-chain-elem-erasure]: explicit []u8 local; non-unsafe // decode + `??` fallback (ASCII FFI buffer), see @ip above. ro addr_bytes []u8 = buf[..n] addr_bytes.to_str() ?? "" }