///|
pub(all) enum NativeLinkSessionKind {
NativeSessionNpcap
NativeSessionRawSocket
NativeSessionUdp
} derive(Eq, Debug)
///|
pub fn NativeLinkSessionKind::label(self : NativeLinkSessionKind) -> String {
match self {
NativeSessionNpcap => "npcap"
NativeSessionRawSocket => "raw-socket"
NativeSessionUdp => "udp"
}
}
///|
pub(all) struct NativeLinkSessionConfig {
kind : NativeLinkSessionKind
interface_name : String
bind_ip : Bytes
bind_port : Int
remote_ip : Bytes
remote_port : Int
ethertype : Int
promiscuous : Bool
snaplen : Int
open_timeout_ms : Int
receive_timeout_ms : Int
filter_expression : String
allow_live_open : Bool
} derive(Eq, Debug)
///|
pub(all) struct NativeLinkSessionDescriptor {
id : String
kind : NativeLinkSessionKind
driver_id : String
c_abi_owner_ids : Array[String]
native_ffi_file : String
fallback_ffi_file : String
supports_open : Bool
supports_send : Bool
supports_receive : Bool
supports_close : Bool
supports_filter : Bool
supports_frame_pool_route : Bool
manual_interface_required : Bool
default_live_open_allowed : Bool
local_loopback_supported : Bool
fallback_mirror_available : Bool
evidence_label : String
} derive(Eq, Debug)
///|
pub(all) struct NativeLinkSessionCatalogReport {
descriptor_count : Int
npcap_session_count : Int
raw_socket_session_count : Int
udp_session_count : Int
open_count : Int
send_count : Int
receive_count : Int
close_count : Int
filter_count : Int
frame_pool_route_count : Int
manual_interface_required_count : Int
default_live_open_allowed_count : Int
local_loopback_count : Int
fallback_mirror_count : Int
missing_c_abi_owner_count : Int
} derive(Eq, Debug)
///|
pub(all) struct NativeLinkSessionFramePoolSmokeReport {
frame_pool_available : Bool
frame_pool_route_supported : Bool
staged_len : Int
read_len : Int
payload_digest : Int
slot_released : Bool
free_count_after_release : Int
zero_copy_live_evidence : Bool
evidence_label : String
} derive(Eq, Debug)
///|
pub(all) struct NativeUdpLinkSessionSmokeReport {
native_backend_present : Bool
udp_session_supported : Bool
loopback_session_passed : Bool
sent_len : Int
received_len : Int
payload_digest : Int
live_nic_evidence : Bool
evidence_label : String
} derive(Eq, Debug)
///|
pub(all) struct NativeNpcapLinkSession {
adapter : NativeNpcapLiveAdapter
} derive(Eq, Debug)
///|
pub(all) struct NativeRawSocketLinkSession {
adapter : NativeRawSocketAdapter
} derive(Eq, Debug)
///|
pub(all) struct NativeUdpLinkSession {
socket : NativeUdpSocket
remote_ip : Bytes
remote_port : Int
} derive(Eq, Debug)
///|
pub fn native_link_session_config(
kind : NativeLinkSessionKind,
) -> NativeLinkSessionConfig {
{
kind,
interface_name: "",
bind_ip: native_loopback_ipv4(),
bind_port: 0,
remote_ip: native_loopback_ipv4(),
remote_port: 0,
ethertype: 0x88b5,
promiscuous: true,
snaplen: 65_535,
open_timeout_ms: 50,
receive_timeout_ms: 50,
filter_expression: "",
allow_live_open: false,
}
}
///|
pub fn native_link_session_catalog() -> Array[NativeLinkSessionDescriptor] {
[
{
id: "native.link-session.npcap",
kind: NativeSessionNpcap,
driver_id: "driver.native.npcap",
c_abi_owner_ids: [
"native.stub.npcap_windows_live", "native.stub.npcap_windows_live_send",
"native.stub.npcap_windows_live_filter", "native.stub.npcap_windows_live_capture",
"native.stub.npcap_unsupported",
],
native_ffi_file: "ffi_npcap_native.mbt",
fallback_ffi_file: "ffi_npcap_fallback.mbt",
supports_open: true,
supports_send: true,
supports_receive: true,
supports_close: true,
supports_filter: true,
supports_frame_pool_route: true,
manual_interface_required: true,
default_live_open_allowed: false,
local_loopback_supported: false,
fallback_mirror_available: true,
evidence_label: "native.link-session.npcap",
},
{
id: "native.link-session.raw-socket",
kind: NativeSessionRawSocket,
driver_id: "driver.native.raw_socket",
c_abi_owner_ids: ["native.stub.raw_socket_linux"],
native_ffi_file: "ffi_raw_socket_native.mbt",
fallback_ffi_file: "ffi_raw_socket_fallback.mbt",
supports_open: true,
supports_send: true,
supports_receive: true,
supports_close: true,
supports_filter: false,
supports_frame_pool_route: true,
manual_interface_required: true,
default_live_open_allowed: false,
local_loopback_supported: false,
fallback_mirror_available: true,
evidence_label: "native.link-session.raw-socket",
},
{
id: "native.link-session.udp",
kind: NativeSessionUdp,
driver_id: "driver.native.udp_socket",
c_abi_owner_ids: [
"native.stub.udp_windows_adapter", "native.stub.udp_windows_send", "native.stub.udp_windows_receive",
"native.stub.udp_posix_adapter", "native.stub.udp_posix_send", "native.stub.udp_posix_receive",
"native.stub.udp_unsupported",
],
native_ffi_file: "ffi_udp_native.mbt",
fallback_ffi_file: "ffi_udp_fallback.mbt",
supports_open: true,
supports_send: true,
supports_receive: true,
supports_close: true,
supports_filter: false,
supports_frame_pool_route: true,
manual_interface_required: false,
default_live_open_allowed: true,
local_loopback_supported: true,
fallback_mirror_available: true,
evidence_label: "native.link-session.udp",
},
]
}
///|
pub fn NativeLinkSessionDescriptor::passes(
self : NativeLinkSessionDescriptor,
) -> Bool {
self.id != "" &&
self.driver_id != "" &&
self.c_abi_owner_ids.length() > 0 &&
self.native_ffi_file != "" &&
self.fallback_ffi_file != "" &&
self.supports_open &&
self.supports_send &&
self.supports_receive &&
self.supports_close &&
self.supports_frame_pool_route &&
self.fallback_mirror_available &&
self.evidence_label.has_prefix("native.link-session.") &&
self.default_live_open_allowed == self.local_loopback_supported
}
///|
pub fn NativeLinkSessionCatalogReport::passes(
self : NativeLinkSessionCatalogReport,
) -> Bool {
self.descriptor_count == 3 &&
self.npcap_session_count == 1 &&
self.raw_socket_session_count == 1 &&
self.udp_session_count == 1 &&
self.open_count == 3 &&
self.send_count == 3 &&
self.receive_count == 3 &&
self.close_count == 3 &&
self.filter_count == 1 &&
self.frame_pool_route_count == 3 &&
self.manual_interface_required_count == 2 &&
self.default_live_open_allowed_count == 1 &&
self.local_loopback_count == 1 &&
self.fallback_mirror_count == 3 &&
self.missing_c_abi_owner_count == 0
}
///|
pub fn native_link_session_catalog_report() -> NativeLinkSessionCatalogReport {
let catalog = native_link_session_catalog()
{
descriptor_count: catalog.length(),
npcap_session_count: count_link_session_descriptors(catalog, fn(entry) {
entry.kind == NativeSessionNpcap
}),
raw_socket_session_count: count_link_session_descriptors(catalog, fn(
entry,
) {
entry.kind == NativeSessionRawSocket
}),
udp_session_count: count_link_session_descriptors(catalog, fn(entry) {
entry.kind == NativeSessionUdp
}),
open_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_open
}),
send_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_send
}),
receive_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_receive
}),
close_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_close
}),
filter_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_filter
}),
frame_pool_route_count: count_link_session_descriptors(catalog, fn(entry) {
entry.supports_frame_pool_route
}),
manual_interface_required_count: count_link_session_descriptors(catalog, fn(
entry,
) {
entry.manual_interface_required
}),
default_live_open_allowed_count: count_link_session_descriptors(catalog, fn(
entry,
) {
entry.default_live_open_allowed
}),
local_loopback_count: count_link_session_descriptors(catalog, fn(entry) {
entry.local_loopback_supported
}),
fallback_mirror_count: count_link_session_descriptors(catalog, fn(entry) {
entry.fallback_mirror_available
}),
missing_c_abi_owner_count: count_missing_link_session_owners(catalog),
}
}
///|
pub fn NativeNpcapLinkSession::open(
config : NativeLinkSessionConfig,
) -> NativeNpcapLinkSession raise NativeLinkError {
if config.kind != NativeSessionNpcap {
raise link_session_error(
"npcap_link_session_open", -92, "session kind mismatch",
)
}
if config.interface_name == "" {
raise link_session_error(
"npcap_link_session_open", -91, "manual interface required",
)
}
if !config.allow_live_open {
raise link_session_error(
"npcap_link_session_open", -90, "manual live open permission required",
)
}
let adapter = NativeNpcapLiveAdapter::open(
config.interface_name,
snaplen=config.snaplen,
promiscuous=config.promiscuous,
timeout_ms=config.open_timeout_ms,
)
if config.filter_expression != "" {
adapter.set_filter(config.filter_expression)
}
{ adapter, }
}
///|
pub fn NativeNpcapLinkSession::send_frame(
self : NativeNpcapLinkSession,
frame : Bytes,
) -> Int raise NativeLinkError {
self.adapter.send_packet(frame)
}
///|
pub fn NativeNpcapLinkSession::receive_frame(
self : NativeNpcapLinkSession,
timeout_ms? : Int = 50,
) -> Bytes raise NativeLinkError {
self.adapter.capture_one(timeout_ms~)
}
///|
pub fn NativeNpcapLinkSession::close(
self : NativeNpcapLinkSession,
) -> Unit raise NativeLinkError {
self.adapter.close()
}
///|
pub fn NativeRawSocketLinkSession::open(
config : NativeLinkSessionConfig,
) -> NativeRawSocketLinkSession raise NativeLinkError {
if config.kind != NativeSessionRawSocket {
raise link_session_error(
"raw_socket_link_session_open", -92, "session kind mismatch",
)
}
if config.interface_name == "" {
raise link_session_error(
"raw_socket_link_session_open", -91, "manual interface required",
)
}
if !config.allow_live_open {
raise link_session_error(
"raw_socket_link_session_open", -90, "manual live open permission required",
)
}
let adapter = NativeRawSocketAdapter::open(
config.interface_name,
ethertype=config.ethertype,
promiscuous=config.promiscuous,
timeout_ms=config.open_timeout_ms,
)
{ adapter, }
}
///|
pub fn NativeRawSocketLinkSession::send_frame(
self : NativeRawSocketLinkSession,
frame : Bytes,
) -> Int raise NativeLinkError {
self.adapter.send_packet(frame)
}
///|
pub fn NativeRawSocketLinkSession::receive_frame(
self : NativeRawSocketLinkSession,
timeout_ms? : Int = 50,
) -> Bytes raise NativeLinkError {
self.adapter.capture_one(timeout_ms~)
}
///|
pub fn NativeRawSocketLinkSession::close(
self : NativeRawSocketLinkSession,
) -> Unit raise NativeLinkError {
self.adapter.close()
}
///|
pub fn NativeUdpLinkSession::open(
config : NativeLinkSessionConfig,
) -> NativeUdpLinkSession raise NativeLinkError {
if config.kind != NativeSessionUdp {
raise link_session_error(
"udp_link_session_open", -92, "session kind mismatch",
)
}
let socket = NativeUdpSocket::open(config.bind_ip, bind_port=config.bind_port)
{ socket, remote_ip: config.remote_ip, remote_port: config.remote_port }
}
///|
pub fn NativeUdpLinkSession::bound_port(
self : NativeUdpLinkSession,
) -> Int raise NativeLinkError {
self.socket.bound_port()
}
///|
pub fn NativeUdpLinkSession::send_frame(
self : NativeUdpLinkSession,
frame : Bytes,
) -> Int raise NativeLinkError {
self.socket.send_to(self.remote_ip, self.remote_port, frame)
}
///|
pub fn NativeUdpLinkSession::receive_frame(
self : NativeUdpLinkSession,
timeout_ms? : Int = 50,
) -> Bytes raise NativeLinkError {
self.socket.receive(timeout_ms)
}
///|
pub fn NativeUdpLinkSession::close(
self : NativeUdpLinkSession,
) -> Unit raise NativeLinkError {
self.socket.close()
}
///|
pub fn NativeFramePool::stage_link_session_frame(
self : NativeFramePool,
frame : Bytes,
) -> NativeFrameRef raise NativeLinkError {
let fref0 = self.acquire()
let buffer = link_session_bytes_to_fixed_array(frame)
let fref1 = self.write(fref0, 0, buffer, frame.length())
self.mark_filled(fref1, fref1.length)
}
///|
pub fn NativeFramePool::read_link_session_frame(
self : NativeFramePool,
fref : NativeFrameRef,
) -> Bytes raise NativeLinkError {
let length = self.slot_length(fref)
let buffer : FixedArray[Byte] = FixedArray::make(length, b'\x00')
let read = self.read_into(fref, 0, buffer, length)
link_session_fixed_array_to_bytes(buffer, read)
}
///|
pub fn native_link_session_frame_pool_smoke() -> NativeLinkSessionFramePoolSmokeReport raise NativeLinkError {
let pool = NativeFramePool::create(capacity=1, frame_size=64)
let payload : Bytes = [0xde, 0xad, 0xbe, 0xef, 0x4c, 0x57, 0x61]
let fref = pool.stage_link_session_frame(payload)
let got = pool.read_link_session_frame(fref)
pool.release(fref)
let free_count = pool.free_count()
pool.destroy()
{
frame_pool_available: true,
frame_pool_route_supported: got == payload,
staged_len: fref.length,
read_len: got.length(),
payload_digest: bytes_digest(got),
slot_released: true,
free_count_after_release: free_count,
zero_copy_live_evidence: false,
evidence_label: "native.link-session.frame-pool-smoke",
}
}
///|
pub fn native_udp_link_session_loopback_smoke(
timeout_ms? : Int = 1_000,
) -> NativeUdpLinkSessionSmokeReport raise NativeLinkError {
let rx_config = native_link_session_config(NativeSessionUdp)
let rx = NativeUdpLinkSession::open(rx_config)
let port = rx.bound_port()
let mut tx_config = native_link_session_config(NativeSessionUdp)
tx_config = { ..tx_config, remote_port: port }
let tx = NativeUdpLinkSession::open(tx_config)
let payload : Bytes = [0x4c, 0x57, 0x2d, 0x53, 0x45, 0x53, 0x53]
let sent = tx.send_frame(payload)
let received = rx.receive_frame(timeout_ms~)
tx.close()
rx.close()
{
native_backend_present: native_stub_version() > 0,
udp_session_supported: true,
loopback_session_passed: sent == payload.length() && received == payload,
sent_len: sent,
received_len: received.length(),
payload_digest: bytes_digest(received),
live_nic_evidence: false,
evidence_label: "native.link-session.udp-loopback-smoke",
}
}
///|
pub fn NativeLinkSessionFramePoolSmokeReport::passes(
self : NativeLinkSessionFramePoolSmokeReport,
) -> Bool {
self.frame_pool_available &&
self.frame_pool_route_supported &&
self.staged_len == self.read_len &&
self.staged_len > 0 &&
self.payload_digest > 0 &&
self.slot_released &&
self.free_count_after_release == 1 &&
!self.zero_copy_live_evidence &&
self.evidence_label == "native.link-session.frame-pool-smoke"
}
///|
pub fn NativeUdpLinkSessionSmokeReport::passes(
self : NativeUdpLinkSessionSmokeReport,
) -> Bool {
self.native_backend_present &&
self.udp_session_supported &&
self.loopback_session_passed &&
self.sent_len == self.received_len &&
self.payload_digest > 0 &&
!self.live_nic_evidence &&
self.evidence_label == "native.link-session.udp-loopback-smoke"
}
///|
fn link_session_error(
op : String,
code : Int,
message : String,
) -> NativeLinkError {
NativeLinkError::SystemError(op, code, message)
}
///|
fn count_link_session_descriptors(
descriptors : ArrayView[NativeLinkSessionDescriptor],
predicate : (NativeLinkSessionDescriptor) -> Bool,
) -> Int {
let mut count = 0
for descriptor in descriptors {
if predicate(descriptor) {
count += 1
}
}
count
}
///|
fn count_missing_link_session_owners(
descriptors : ArrayView[NativeLinkSessionDescriptor],
) -> Int {
let mut count = 0
for descriptor in descriptors {
for owner in descriptor.c_abi_owner_ids {
if !link_session_contains_module(owner) {
count += 1
}
}
}
count
}
///|
fn link_session_contains_module(id : String) -> Bool {
for entry in native_stub_module_catalog() {
if entry.id == id {
return true
}
}
false
}
///|
fn link_session_bytes_to_fixed_array(bytes : Bytes) -> FixedArray[Byte] {
let out = FixedArray::make(bytes.length(), b'\x00')
for i in 0.. Bytes {
let out : Array[Byte] = []
let limit = min_int(max_int(count, 0), bytes.length())
for i in 0..