use crate::error::HookError;
use crate::hooks::HookContext;
#[cfg(feature = "wasm")]
use crate::result::{HookResult, Verdict};
#[cfg(feature = "wasm")]
use crate::runtime::StoreData;
pub use rns_hooks_abi::context::{
AnnounceContext, BackbonePeerContext, InterfaceContext, LinkContext, PacketContext,
TickContext, ARENA_BASE, CTX_TYPE_ANNOUNCE, CTX_TYPE_BACKBONE_PEER, CTX_TYPE_INTERFACE,
CTX_TYPE_LINK, CTX_TYPE_PACKET, CTX_TYPE_TICK,
};
#[cfg(feature = "wasm")]
pub fn write_context(
memory: &wasmtime::Memory,
mut store: impl wasmtime::AsContextMut<Data = StoreData>,
ctx: &HookContext,
) -> Result<usize, HookError> {
let mem_size = memory.data_size(&store);
match ctx {
HookContext::Packet { ctx: pkt, raw } => {
let header_size = std::mem::size_of::<PacketContext>();
let size = header_size + raw.len();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for Packet context".into(),
));
}
let data = memory.data_mut(&mut store);
let base = ARENA_BASE;
write_u32(data, base, CTX_TYPE_PACKET);
data[base + 4] = pkt.flags;
data[base + 5] = pkt.hops;
data[base + 6] = 0;
data[base + 7] = 0;
data[base + 8..base + 24].copy_from_slice(&pkt.destination_hash);
data[base + 24] = pkt.context;
data[base + 25] = 0;
data[base + 26] = 0;
data[base + 27] = 0;
data[base + 28..base + 60].copy_from_slice(&pkt.packet_hash);
write_u32(data, base + 60, 0);
write_u64(data, base + 64, pkt.interface_id);
write_u32(data, base + 72, header_size as u32);
write_u32(data, base + 76, raw.len() as u32);
if !raw.is_empty() {
let data_start = base + header_size;
data[data_start..data_start + raw.len()].copy_from_slice(raw);
}
Ok(size)
}
HookContext::Interface { interface_id } => {
let size = std::mem::size_of::<InterfaceContext>();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for Interface context".into(),
));
}
let data = memory.data_mut(&mut store);
let base = ARENA_BASE;
write_u32(data, base, CTX_TYPE_INTERFACE);
write_u32(data, base + 4, 0); write_u64(data, base + 8, *interface_id);
Ok(size)
}
HookContext::Tick => {
let size = std::mem::size_of::<TickContext>();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for Tick context".into(),
));
}
let data = memory.data_mut(&mut store);
write_u32(data, ARENA_BASE, CTX_TYPE_TICK);
Ok(size)
}
HookContext::Announce {
destination_hash,
hops,
interface_id,
} => {
let size = std::mem::size_of::<AnnounceContext>();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for Announce context".into(),
));
}
let data = memory.data_mut(&mut store);
let base = ARENA_BASE;
write_u32(data, base, CTX_TYPE_ANNOUNCE);
data[base + 4] = *hops;
data[base + 5] = 0;
data[base + 6] = 0;
data[base + 7] = 0;
data[base + 8..base + 24].copy_from_slice(destination_hash);
write_u64(data, base + 24, *interface_id);
Ok(size)
}
HookContext::Link {
link_id,
interface_id,
} => {
let size = std::mem::size_of::<LinkContext>();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for Link context".into(),
));
}
let data = memory.data_mut(&mut store);
let base = ARENA_BASE;
write_u32(data, base, CTX_TYPE_LINK);
write_u32(data, base + 4, 0); data[base + 8..base + 24].copy_from_slice(link_id);
write_u64(data, base + 24, *interface_id);
Ok(size)
}
HookContext::BackbonePeer {
server_interface_id,
peer_interface_id,
peer_ip,
peer_port,
connected_for,
had_received_data,
penalty_level,
blacklist_for,
} => {
let size = std::mem::size_of::<BackbonePeerContext>();
if ARENA_BASE + size > mem_size {
return Err(HookError::InvalidResult(
"arena overflow for BackbonePeer context".into(),
));
}
let data = memory.data_mut(&mut store);
let base = ARENA_BASE;
write_u32(data, base, CTX_TYPE_BACKBONE_PEER);
data[base + 4] = peer_ip_family(peer_ip);
write_u16(data, base + 5, *peer_port);
data[base + 7] = u8::from(*had_received_data);
write_u64(data, base + 8, *server_interface_id);
write_u64(data, base + 16, peer_interface_id.unwrap_or(0));
write_u64(data, base + 24, connected_for.as_secs());
data[base + 32] = *penalty_level;
data[base + 33..base + 40].fill(0);
write_u64(data, base + 40, blacklist_for.as_secs());
data[base + 48..base + 64].copy_from_slice(&peer_ip_bytes(peer_ip));
Ok(size)
}
}
}
pub fn context_to_bytes(
ctx: &HookContext,
data_override: Option<&[u8]>,
) -> Result<Vec<u8>, HookError> {
match ctx {
HookContext::Packet { ctx: pkt, raw } => {
let raw = data_override.unwrap_or(raw);
let header_size = std::mem::size_of::<PacketContext>();
let mut data = vec![0u8; header_size + raw.len()];
write_u32(&mut data, 0, CTX_TYPE_PACKET);
data[4] = pkt.flags;
data[5] = pkt.hops;
data[8..24].copy_from_slice(&pkt.destination_hash);
data[24] = pkt.context;
data[28..60].copy_from_slice(&pkt.packet_hash);
write_u64(&mut data, 64, pkt.interface_id);
write_u32(&mut data, 72, header_size as u32);
write_u32(&mut data, 76, raw.len() as u32);
if !raw.is_empty() {
data[header_size..header_size + raw.len()].copy_from_slice(raw);
}
Ok(data)
}
HookContext::Interface { interface_id } => {
let mut data = vec![0u8; std::mem::size_of::<InterfaceContext>()];
write_u32(&mut data, 0, CTX_TYPE_INTERFACE);
write_u64(&mut data, 8, *interface_id);
Ok(data)
}
HookContext::Tick => {
let mut data = vec![0u8; std::mem::size_of::<TickContext>()];
write_u32(&mut data, 0, CTX_TYPE_TICK);
Ok(data)
}
HookContext::Announce {
destination_hash,
hops,
interface_id,
} => {
let mut data = vec![0u8; std::mem::size_of::<AnnounceContext>()];
write_u32(&mut data, 0, CTX_TYPE_ANNOUNCE);
data[4] = *hops;
data[8..24].copy_from_slice(destination_hash);
write_u64(&mut data, 24, *interface_id);
Ok(data)
}
HookContext::Link {
link_id,
interface_id,
} => {
let mut data = vec![0u8; std::mem::size_of::<LinkContext>()];
write_u32(&mut data, 0, CTX_TYPE_LINK);
data[8..24].copy_from_slice(link_id);
write_u64(&mut data, 24, *interface_id);
Ok(data)
}
HookContext::BackbonePeer {
server_interface_id,
peer_interface_id,
peer_ip,
peer_port,
connected_for,
had_received_data,
penalty_level,
blacklist_for,
} => {
let mut data = vec![0u8; std::mem::size_of::<BackbonePeerContext>()];
write_u32(&mut data, 0, CTX_TYPE_BACKBONE_PEER);
data[4] = peer_ip_family(peer_ip);
write_u16(&mut data, 5, *peer_port);
data[7] = u8::from(*had_received_data);
write_u64(&mut data, 8, *server_interface_id);
write_u64(&mut data, 16, peer_interface_id.unwrap_or(0));
write_u64(&mut data, 24, connected_for.as_secs());
data[32] = *penalty_level;
write_u64(&mut data, 40, blacklist_for.as_secs());
data[48..64].copy_from_slice(&peer_ip_bytes(peer_ip));
Ok(data)
}
}
}
#[cfg(feature = "wasm")]
pub fn read_result(
memory: &wasmtime::Memory,
store: impl wasmtime::AsContext<Data = StoreData>,
offset: usize,
) -> Result<HookResult, HookError> {
let size = std::mem::size_of::<HookResult>();
let mem_size = memory.data_size(&store);
if offset + size > mem_size {
return Err(HookError::InvalidResult(format!(
"result offset {} + size {} exceeds memory size {}",
offset, size, mem_size
)));
}
let data = memory.data(&store);
let verdict = read_u32(data, offset);
if Verdict::from_u32(verdict).is_none() {
return Err(HookError::InvalidResult(format!(
"invalid verdict value: {}",
verdict
)));
}
Ok(HookResult {
verdict,
modified_data_offset: read_u32(data, offset + 4),
modified_data_len: read_u32(data, offset + 8),
inject_actions_offset: read_u32(data, offset + 12),
inject_actions_count: read_u32(data, offset + 16),
log_offset: read_u32(data, offset + 20),
log_len: read_u32(data, offset + 24),
})
}
fn write_u32(data: &mut [u8], offset: usize, val: u32) {
data[offset..offset + 4].copy_from_slice(&val.to_le_bytes());
}
fn write_u16(data: &mut [u8], offset: usize, val: u16) {
data[offset..offset + 2].copy_from_slice(&val.to_le_bytes());
}
fn write_u64(data: &mut [u8], offset: usize, val: u64) {
data[offset..offset + 8].copy_from_slice(&val.to_le_bytes());
}
fn peer_ip_family(ip: &std::net::IpAddr) -> u8 {
match ip {
std::net::IpAddr::V4(_) => 4,
std::net::IpAddr::V6(_) => 6,
}
}
fn peer_ip_bytes(ip: &std::net::IpAddr) -> [u8; 16] {
match ip {
std::net::IpAddr::V4(v4) => {
let mut out = [0u8; 16];
out[..4].copy_from_slice(&v4.octets());
out
}
std::net::IpAddr::V6(v6) => v6.octets(),
}
}
#[cfg(feature = "wasm")]
fn read_u32(data: &[u8], offset: usize) -> u32 {
u32::from_le_bytes(data[offset..offset + 4].try_into().unwrap())
}
fn read_slice(data: &[u8], offset: usize, len: usize) -> Option<Vec<u8>> {
let end = offset.checked_add(len)?;
if end > data.len() {
return None;
}
Some(data[offset..end].to_vec())
}
pub fn read_action_wire(
wasm_data: &[u8],
action_ptr: usize,
action_len: usize,
) -> Option<crate::wire::ActionWire> {
use crate::wire::*;
use rns_hooks_abi::wire as tags;
let end = action_ptr.checked_add(action_len)?;
if end > wasm_data.len() || action_len == 0 {
return None;
}
let buf = &wasm_data[action_ptr..end];
let tag = buf[0];
let b = &buf[1..];
match tag {
tags::TAG_SEND_ON_INTERFACE => {
if b.len() < 16 {
return None;
}
let interface = u64::from_le_bytes(b[0..8].try_into().ok()?);
let data_offset = u32::from_le_bytes(b[8..12].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[12..16].try_into().ok()?) as usize;
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::SendOnInterface { interface, raw })
}
tags::TAG_BROADCAST => {
if b.len() < 17 {
return None;
}
let data_offset = u32::from_le_bytes(b[0..4].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize;
let exclude = u64::from_le_bytes(b[8..16].try_into().ok()?);
let has_exclude = b[16];
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::BroadcastOnAllInterfaces {
raw,
exclude,
has_exclude,
})
}
tags::TAG_DELIVER_LOCAL => {
if b.len() < 64 {
return None;
}
let destination_hash: [u8; 16] = b[0..16].try_into().ok()?;
let data_offset = u32::from_le_bytes(b[16..20].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[20..24].try_into().ok()?) as usize;
let packet_hash: [u8; 32] = b[24..56].try_into().ok()?;
let receiving_interface = u64::from_le_bytes(b[56..64].try_into().ok()?);
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::DeliverLocal {
destination_hash,
raw,
packet_hash,
receiving_interface,
})
}
tags::TAG_PATH_UPDATED => {
if b.len() < 41 {
return None;
}
let destination_hash: [u8; 16] = b[0..16].try_into().ok()?;
let hops = b[16];
let next_hop: [u8; 16] = b[17..33].try_into().ok()?;
let interface = u64::from_le_bytes(b[33..41].try_into().ok()?);
Some(ActionWire::PathUpdated {
destination_hash,
hops,
next_hop,
interface,
})
}
tags::TAG_ANNOUNCE_RECEIVED => {
if b.len() < 126 {
return None;
}
let destination_hash: [u8; 16] = b[0..16].try_into().ok()?;
let identity_hash: [u8; 16] = b[16..32].try_into().ok()?;
let public_key: [u8; 64] = b[32..96].try_into().ok()?;
let name_hash: [u8; 10] = b[96..106].try_into().ok()?;
let random_hash: [u8; 10] = b[106..116].try_into().ok()?;
let hops = b[116];
let receiving_interface = u64::from_le_bytes(b[117..125].try_into().ok()?);
let has_app_data = b[125];
let app_data = if has_app_data != 0 {
if b.len() < 134 {
return None;
}
let app_data_offset = u32::from_le_bytes(b[126..130].try_into().ok()?) as usize;
let app_data_len = u32::from_le_bytes(b[130..134].try_into().ok()?) as usize;
Some(read_slice(wasm_data, app_data_offset, app_data_len)?)
} else {
None
};
Some(ActionWire::AnnounceReceived {
destination_hash,
identity_hash,
public_key,
name_hash,
random_hash,
app_data,
hops,
receiving_interface,
})
}
tags::TAG_FORWARD_LOCAL_CLIENTS => {
if b.len() < 17 {
return None;
}
let data_offset = u32::from_le_bytes(b[0..4].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize;
let exclude = u64::from_le_bytes(b[8..16].try_into().ok()?);
let has_exclude = b[16];
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::ForwardToLocalClients {
raw,
exclude,
has_exclude,
})
}
tags::TAG_FORWARD_PLAIN_BROADCAST => {
if b.len() < 18 {
return None;
}
let data_offset = u32::from_le_bytes(b[0..4].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[4..8].try_into().ok()?) as usize;
let to_local = b[8];
let exclude = u64::from_le_bytes(b[9..17].try_into().ok()?);
let has_exclude = b[17];
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::ForwardPlainBroadcast {
raw,
to_local,
exclude,
has_exclude,
})
}
tags::TAG_CACHE_ANNOUNCE => {
if b.len() < 40 {
return None;
}
let packet_hash: [u8; 32] = b[0..32].try_into().ok()?;
let data_offset = u32::from_le_bytes(b[32..36].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[36..40].try_into().ok()?) as usize;
let raw = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::CacheAnnounce { packet_hash, raw })
}
tags::TAG_TUNNEL_SYNTHESIZE => {
if b.len() < 32 {
return None;
}
let interface = u64::from_le_bytes(b[0..8].try_into().ok()?);
let data_offset = u32::from_le_bytes(b[8..12].try_into().ok()?) as usize;
let data_len = u32::from_le_bytes(b[12..16].try_into().ok()?) as usize;
let dest_hash: [u8; 16] = b[16..32].try_into().ok()?;
let data = read_slice(wasm_data, data_offset, data_len)?;
Some(ActionWire::TunnelSynthesize {
interface,
data,
dest_hash,
})
}
tags::TAG_TUNNEL_ESTABLISHED => {
if b.len() < 40 {
return None;
}
let tunnel_id: [u8; 32] = b[0..32].try_into().ok()?;
let interface = u64::from_le_bytes(b[32..40].try_into().ok()?);
Some(ActionWire::TunnelEstablished {
tunnel_id,
interface,
})
}
_ => None,
}
}
#[cfg(feature = "wasm")]
pub fn read_modified_data(
memory: &wasmtime::Memory,
store: impl wasmtime::AsContext<Data = StoreData>,
result: &crate::result::HookResult,
) -> Option<Vec<u8>> {
let offset = result.modified_data_offset as usize;
let len = result.modified_data_len as usize;
if len == 0 {
return None;
}
let data = memory.data(&store);
let end = offset.checked_add(len)?;
if end > data.len() {
return None;
}
Some(data[offset..end].to_vec())
}
#[cfg(feature = "wasm")]
pub fn write_data_override(
memory: &wasmtime::Memory,
mut store: impl wasmtime::AsContextMut<Data = StoreData>,
new_data: &[u8],
) -> Result<(), HookError> {
let header_size = std::mem::size_of::<PacketContext>();
let data_start = ARENA_BASE + header_size;
let mem_size = memory.data_size(&store);
if data_start + new_data.len() > mem_size {
return Err(HookError::InvalidResult(
"modified data overflows arena".into(),
));
}
let mem = memory.data_mut(&mut store);
mem[data_start..data_start + new_data.len()].copy_from_slice(new_data);
write_u32(mem, ARENA_BASE + 76, new_data.len() as u32);
Ok(())
}