mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-13 07:48:30 +00:00
1513 lines
85 KiB
Rust
1513 lines
85 KiB
Rust
#![cfg_attr(target_os = "none", no_std)]
|
|
#![cfg_attr(target_os = "none", no_main)]
|
|
|
|
mod api;
|
|
use api::*;
|
|
use num_traits::*;
|
|
use com::api::{ComIntSources, Ipv4Conf, NET_MTU};
|
|
|
|
mod device;
|
|
mod connection_manager;
|
|
|
|
use xous::{Message, CID, SID, msg_scalar_unpack, msg_blocking_scalar_unpack, send_message};
|
|
use xous_ipc::Buffer;
|
|
use std::collections::BTreeMap;
|
|
use std::collections::HashMap;
|
|
use std::convert::TryInto;
|
|
|
|
use smoltcp::phy::{Medium, Device};
|
|
use smoltcp::iface::{InterfaceBuilder, NeighborCache, Routes, Interface};
|
|
use smoltcp::socket::{IcmpEndpoint, IcmpPacketMetadata, IcmpSocket, IcmpSocketBuffer, SocketSet};
|
|
use smoltcp::wire::{
|
|
EthernetAddress, IpAddress, IpCidr, Ipv4Address, Ipv4Cidr, IpEndpoint
|
|
};
|
|
use smoltcp::wire::{
|
|
Icmpv4Packet, Icmpv4Repr, Icmpv6Packet, Icmpv6Repr,
|
|
};
|
|
use byteorder::{ByteOrder, NetworkEndian};
|
|
|
|
use smoltcp::socket::{UdpPacketMetadata, UdpSocket, UdpSocketBuffer, SocketHandle, TcpSocket, TcpSocketBuffer};
|
|
use smoltcp::time::{Instant, Duration};
|
|
use std::thread;
|
|
use std::sync::Arc;
|
|
use core::sync::atomic::{AtomicU32, Ordering};
|
|
|
|
const PING_DEFAULT_TIMEOUT_MS: u32 = 10_000;
|
|
|
|
fn set_ipv4_addr<DeviceT>(iface: &mut Interface<'_, DeviceT>, cidr: Ipv4Cidr)
|
|
where
|
|
DeviceT: for<'d> Device<'d>,
|
|
{
|
|
iface.update_ip_addrs(|addrs| {
|
|
let dest = addrs.iter_mut().next().expect("trouble updating ipv4 addresses in routing table");
|
|
*dest = IpCidr::Ipv4(cidr);
|
|
});
|
|
}
|
|
|
|
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)]
|
|
enum WaitOp {
|
|
WaitMs,
|
|
PollAt,
|
|
Quit,
|
|
}
|
|
|
|
/// UdpState will return a full custom datastructure, and is designed to work with
|
|
/// a one-time use dedicated server created as part of the Net library code.
|
|
pub struct UdpState {
|
|
handle: SocketHandle,
|
|
cid: CID,
|
|
sid: SID,
|
|
}
|
|
|
|
/// PingConnection can return a Scalar: because of the simplicity of the return data
|
|
/// we give implementors the option to unpack the Scalar themselves within the main loop
|
|
/// of their event handler, *or* they can create a dedicated server that handles the return
|
|
/// code.
|
|
///
|
|
/// Unpacking the Scalar type is more efficient, but essentially requires a connection
|
|
/// to their private main loop server connection for the message to arrive, brokered via
|
|
/// xous-names. This can create a potential security concern, as the "unclaimed" connection
|
|
/// could be abused by a malicious process, which would have access to all of the dispatchable
|
|
/// opcodes of the main loop through that connection.
|
|
///
|
|
/// Thus, for security-sensitive processes, it is recommended that those create a single-purpose
|
|
/// server ID and broker the connection through that mechanism.
|
|
#[derive(Hash, PartialEq, Eq)]
|
|
pub struct PingConnection {
|
|
remote: IpAddress,
|
|
cid: CID,
|
|
retop: usize,
|
|
}
|
|
|
|
#[derive(Hash, PartialEq, Eq)]
|
|
pub struct TcpConnection {
|
|
remote: IpAddress,
|
|
remote_port: u16,
|
|
local_port: u16,
|
|
}
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub struct TcpState {
|
|
handle: SocketHandle,
|
|
cid: CID,
|
|
shutdown_rx: bool,
|
|
}
|
|
|
|
fn set_com_ints(com_int_list: &mut Vec::<ComIntSources>) {
|
|
com_int_list.clear();
|
|
com_int_list.push(ComIntSources::WlanIpConfigUpdate);
|
|
com_int_list.push(ComIntSources::WlanRxReady);
|
|
com_int_list.push(ComIntSources::BatteryCritical);
|
|
com_int_list.push(ComIntSources::Connect);
|
|
com_int_list.push(ComIntSources::Disconnect);
|
|
com_int_list.push(ComIntSources::WlanSsidScanUpdate);
|
|
com_int_list.push(ComIntSources::WlanSsidScanFinished);
|
|
com_int_list.push(ComIntSources::WfxErr);
|
|
}
|
|
|
|
#[xous::xous_main]
|
|
fn xmain() -> ! {
|
|
log_server::init_wait().unwrap();
|
|
log::set_max_level(log::LevelFilter::Info);
|
|
log::info!("my PID is {}", xous::process::id());
|
|
|
|
let xns = xous_names::XousNames::new().unwrap();
|
|
let net_sid = xns.register_name(api::SERVER_NAME_NET, None).expect("can't register server");
|
|
let net_conn = xous::connect(net_sid).unwrap();
|
|
log::trace!("registered with NS -- {:?}", net_sid);
|
|
|
|
// bring the EC into a sane state for the network -- that is, reset the EC
|
|
let mut llio = llio::Llio::new(&xns);
|
|
let com = com::Com::new(&xns).unwrap();
|
|
let timer = ticktimer_server::Ticktimer::new().unwrap();
|
|
|
|
// we need a trng for port numbers
|
|
let trng = trng::Trng::new(&xns).unwrap();
|
|
|
|
// hook the COM interrupt listener
|
|
let net_cid = xous::connect(net_sid).unwrap();
|
|
llio.hook_com_event_callback(Opcode::ComInterrupt.to_u32().unwrap(), net_cid).unwrap();
|
|
llio.com_event_enable(true).unwrap();
|
|
// setup the interrupt masks
|
|
let mut com_int_list: Vec::<ComIntSources> = vec![];
|
|
com.ints_get_active(&mut com_int_list).ok();
|
|
log::debug!("COM initial pending interrupts: {:?}", com_int_list);
|
|
set_com_ints(&mut com_int_list);
|
|
com.ints_enable(&com_int_list);
|
|
com_int_list.clear();
|
|
com.ints_get_active(&mut com_int_list).ok();
|
|
log::debug!("COM pending interrupts after enabling: {:?}", com_int_list);
|
|
const MAX_DELAY_THREADS: u32 = 10; // limit the number of concurrent delay threads. Typically we have 1-2 running at any time, but DoS conditions could lead to many more.
|
|
let delay_threads = Arc::new(AtomicU32::new(0));
|
|
let mut net_config: Option<Ipv4Conf> = None;
|
|
|
|
// storage for all our sockets
|
|
let mut sockets = SocketSet::new(vec![]);
|
|
|
|
// ping storage
|
|
// up to four concurrent pings in the queue
|
|
let icmp_rx_buffer = IcmpSocketBuffer::new(
|
|
vec![
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY
|
|
],
|
|
vec![0; 1024]);
|
|
let icmp_tx_buffer = IcmpSocketBuffer::new(
|
|
vec![
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY,
|
|
IcmpPacketMetadata::EMPTY
|
|
],
|
|
vec![0; 1024]);
|
|
let mut icmp_socket = IcmpSocket::new(icmp_rx_buffer, icmp_tx_buffer);
|
|
let ident = 0x22b;
|
|
icmp_socket.bind(IcmpEndpoint::Ident(ident)).expect("couldn't bind to icmp socket");
|
|
let icmp_handle = sockets.add(icmp_socket);
|
|
let mut seq: u16 = 0;
|
|
// this record stores the origin time + IP address of the outgoing ping sequence number
|
|
let mut ping_destinations = HashMap::<PingConnection, HashMap::<u16, u64>>::new();
|
|
let mut ping_timeout_ms = PING_DEFAULT_TIMEOUT_MS;
|
|
|
|
// udp storage
|
|
let mut udp_handles = HashMap::<u16, UdpState>::new();
|
|
// UDP requires multiple copies. The way it works is that Tx can come from anyone;
|
|
// for Rx, copies of a CID,SID tuple are kept for every clone is kept in a HashMap. This
|
|
// allows for the Rx data to be cc:'d to each clone, and identified by SID upon drop
|
|
let mut udp_clones = HashMap::<u16, HashMap::<[u32; 4], CID>>::new(); // additional clones for UDP responders
|
|
|
|
// tcp storage
|
|
let mut tcp_handles = HashMap::<TcpConnection, TcpState>::new();
|
|
let mut tcp_listeners = HashMap::<u16, Vec::<TcpState>>::new();
|
|
|
|
// other link storage
|
|
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
|
let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
|
|
let routes = Routes::new(BTreeMap::new());
|
|
|
|
let device = device::NetPhy::new(&xns);
|
|
// needed by ICMP to determine if we should compute checksums
|
|
let device_caps = device.capabilities();
|
|
let medium = device.capabilities().medium;
|
|
let mut builder = InterfaceBuilder::new(device)
|
|
.ip_addrs(ip_addrs)
|
|
.routes(routes);
|
|
if medium == Medium::Ethernet {
|
|
builder = builder
|
|
.ethernet_addr(EthernetAddress::from_bytes(&[0; 6]))
|
|
.neighbor_cache(neighbor_cache);
|
|
}
|
|
let mut iface = builder.finalize();
|
|
|
|
// DNS hooks - the DNS server can ask the Net crate to tickle it when IP configs change using these hooks
|
|
// Currently, we assume there is only one DNS server in Xous. I suppose you could
|
|
// upgrade the code to handle multiple DNS servers, but...why???
|
|
// ... nevermind, someone will invent a $reason because there was never a shiny
|
|
// new feature that a coder didn't love and *had* to have *right now*.
|
|
let mut dns_ipv4_hook = XousScalarEndpoint::new();
|
|
let mut dns_ipv6_hook = XousScalarEndpoint::new();
|
|
let mut dns_allclear_hook = XousScalarEndpoint::new();
|
|
|
|
log::trace!("ready to accept requests");
|
|
// register a suspend/resume listener
|
|
let sr_cid = xous::connect(net_sid).expect("couldn't create suspend callback connection");
|
|
let mut susres = susres::Susres::new(Some(susres::SuspendOrder::Early), &xns, api::Opcode::SuspendResume as u32, sr_cid).expect("couldn't create suspend/resume object");
|
|
|
|
// kick off the connection manager thread
|
|
let cm_sid = xous::create_server().expect("couldn't create connection manager server");
|
|
let cm_cid = xous::connect(cm_sid).unwrap();
|
|
let activity_interval = Arc::new(AtomicU32::new(0));
|
|
#[cfg(not(feature="renode-minimal"))]
|
|
thread::spawn({
|
|
let activity_interval = activity_interval.clone();
|
|
move || {
|
|
connection_manager::connection_manager(cm_sid, activity_interval);
|
|
}
|
|
});
|
|
|
|
let mut cid_to_disconnect: Option<CID> = None;
|
|
loop {
|
|
let mut msg = xous::receive_message(net_sid).unwrap();
|
|
if let Some(dc_cid) = cid_to_disconnect.take() { // disconnect previous loop iter's connection after d/c OK response was sent
|
|
unsafe{
|
|
match xous::disconnect(dc_cid) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerNotFound) => {
|
|
log::trace!("Disconnect returned the expected error code for a remote that has been destroyed.")
|
|
},
|
|
Err(e) => {
|
|
log::error!("Attempt to de-allocate CID to destroyed server met with error: {:?}", e);
|
|
},
|
|
}
|
|
}
|
|
}
|
|
match FromPrimitive::from_usize(msg.body.id()) {
|
|
Some(Opcode::Ping) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut pkt = buf.to_original::<NetPingPacket, _>().unwrap();
|
|
let mut socket = sockets.get::<IcmpSocket>(icmp_handle);
|
|
if socket.can_send() {
|
|
log::trace!("sending ping to {:?}", pkt.endpoint);
|
|
let remote = IpAddress::from(pkt.endpoint);
|
|
// we take advantage of the fact that the same CID is always returned for repeated connect requests to the same SID.
|
|
let cid = match pkt.server {
|
|
XousServerId::PrivateSid(sid) => match xous::connect(SID::from_array(sid)) {
|
|
Ok(cid) => cid,
|
|
Err(e) => {
|
|
log::error!("Ping request with single-use callback SID is invalid. Aborting request. {:?}",e);
|
|
continue;
|
|
}
|
|
}
|
|
XousServerId::ServerName(name) => match xns.request_connection(name.to_str()) {
|
|
Ok(cid) => cid,
|
|
Err(e) => {
|
|
log::error!("Ping request received, but callback name '{}' is invalid. Aborting request. {:?}", name, e);
|
|
continue;
|
|
}
|
|
}
|
|
};
|
|
// this structure can be a HashMap key because it "should" be invariant across well-formed ping requests
|
|
let conn = PingConnection {
|
|
remote,
|
|
cid,
|
|
retop: pkt.return_opcode,
|
|
};
|
|
log::trace!("ping conn info: remote {:?} / cid: {} / retp: {}", remote, cid, pkt.return_opcode);
|
|
// this code will guarantee the sequence number goes up, but if multiple concurrent
|
|
// pings are in progress, they may not be directly in sequence. This is OK.
|
|
let now = timer.elapsed_ms();
|
|
if let Some(queue) = ping_destinations.get_mut(&conn) {
|
|
queue.insert(seq, now);
|
|
} else {
|
|
let mut new_queue = HashMap::<u16, u64>::new();
|
|
new_queue.insert(seq, now);
|
|
ping_destinations.insert(conn, new_queue);
|
|
};
|
|
|
|
// now emit the actual packet
|
|
let mut echo_payload = [0xffu8; 40];
|
|
NetworkEndian::write_i64(&mut echo_payload, now as i64);
|
|
match remote {
|
|
IpAddress::Ipv4(_) => {
|
|
let icmp_repr = Icmpv4Repr::EchoRequest {
|
|
ident,
|
|
seq_no: seq,
|
|
data: &echo_payload,
|
|
};
|
|
let icmp_payload = socket.send(icmp_repr.buffer_len(), remote).unwrap();
|
|
let mut icmp_packet = Icmpv4Packet::new_unchecked(icmp_payload);
|
|
icmp_repr.emit(&mut icmp_packet, &device_caps.checksum);
|
|
}
|
|
IpAddress::Ipv6(_) => {
|
|
// not sure if this is a valid thing to do, to just assign the source some number like this??
|
|
let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1);
|
|
let icmp_repr = Icmpv6Repr::EchoRequest {
|
|
ident,
|
|
seq_no: seq,
|
|
data: &echo_payload,
|
|
};
|
|
let icmp_payload = socket.send(icmp_repr.buffer_len(), remote).unwrap();
|
|
let mut icmp_packet = Icmpv6Packet::new_unchecked(icmp_payload);
|
|
icmp_repr.emit(
|
|
&src_ipv6,
|
|
&remote,
|
|
&mut icmp_packet,
|
|
&device_caps.checksum,
|
|
);
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
seq += 1;
|
|
// fire off a Pump to get the stack to actually transmit the ping; this call merely queues it for sending
|
|
xous::try_send_message(net_conn,
|
|
Message::new_scalar(
|
|
Opcode::NetPump.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).ok();
|
|
pkt.sent_ok = Some(true);
|
|
} else {
|
|
pkt.sent_ok = Some(false);
|
|
}
|
|
buf.replace(pkt).expect("Xous couldn't issue response to Ping request");
|
|
}
|
|
Some(Opcode::PingSetTtl) => msg_scalar_unpack!(msg, ttl, _, _, _, {
|
|
let checked_ttl = if ttl > 255 {
|
|
255 as u8
|
|
} else {
|
|
ttl as u8
|
|
};
|
|
let mut socket = sockets.get::<IcmpSocket>(icmp_handle);
|
|
socket.set_hop_limit(Some(checked_ttl));
|
|
}),
|
|
Some(Opcode::PingGetTtl) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
|
|
let socket = sockets.get::<IcmpSocket>(icmp_handle);
|
|
let checked_ttl = if let Some(ttl) = socket.hop_limit() {
|
|
ttl
|
|
} else {
|
|
64 // because this is the default according to the smoltcp source code
|
|
};
|
|
xous::return_scalar(msg.sender, checked_ttl as usize).unwrap();
|
|
}),
|
|
Some(Opcode::PingSetTimeout) => msg_scalar_unpack!(msg, to, _, _, _, {
|
|
ping_timeout_ms = to as u32;
|
|
}),
|
|
Some(Opcode::PingGetTimeout) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
|
|
xous::return_scalar(msg.sender, ping_timeout_ms as usize).unwrap();
|
|
}),
|
|
Some(Opcode::DnsHookAddIpv4) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let hook = buf.to_original::<XousPrivateServerHook, _>().unwrap();
|
|
if dns_ipv4_hook.is_set() {
|
|
buf.replace(NetMemResponse::AlreadyUsed).unwrap();
|
|
} else {
|
|
dns_ipv4_hook.set(
|
|
xous::connect(SID::from_array(hook.one_time_sid)).unwrap(),
|
|
hook.op,
|
|
hook.args,
|
|
);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
}
|
|
Some(Opcode::DnsHookAddIpv6) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let hook = buf.to_original::<XousPrivateServerHook, _>().unwrap();
|
|
if dns_ipv6_hook.is_set() {
|
|
buf.replace(NetMemResponse::AlreadyUsed).unwrap();
|
|
} else {
|
|
dns_ipv6_hook.set(
|
|
xous::connect(SID::from_array(hook.one_time_sid)).unwrap(),
|
|
hook.op,
|
|
hook.args,
|
|
);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
}
|
|
Some(Opcode::DnsHookAllClear) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let hook = buf.to_original::<XousPrivateServerHook, _>().unwrap();
|
|
if dns_allclear_hook.is_set() {
|
|
buf.replace(NetMemResponse::AlreadyUsed).unwrap();
|
|
} else {
|
|
dns_allclear_hook.set(
|
|
xous::connect(SID::from_array(hook.one_time_sid)).unwrap(),
|
|
hook.op,
|
|
hook.args,
|
|
);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
|
|
}
|
|
Some(Opcode::DnsUnhookAll) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
|
|
dns_ipv4_hook.clear();
|
|
dns_ipv6_hook.clear();
|
|
dns_allclear_hook.clear();
|
|
xous::return_scalar(msg.sender, 1).expect("couldn't ack unhook");
|
|
}),
|
|
Some(Opcode::TcpConnect) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcpspec = buf.to_original::<NetTcpManage, _>().unwrap(); // need to define this
|
|
let address = IpAddress::from(tcpspec.ip_addr);
|
|
let remote_port = tcpspec.remote_port;
|
|
|
|
// initiates a new connection to a remote server consisting of an (Address:Port) tuple.
|
|
// multiple connections can exist to a server, and they are further differentiated by the return port
|
|
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
|
|
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
|
|
let mut tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
|
if let Some(timeout) = tcpspec.timeout_ms {
|
|
tcp_socket.set_timeout(Some(Duration::from_millis(timeout)));
|
|
}
|
|
if let Some(keepalive) = tcpspec.keepalive_ms {
|
|
tcp_socket.set_keep_alive(Some(Duration::from_millis(keepalive)));
|
|
}
|
|
let local_port = (49152 + trng.get_u32().unwrap() % 16384) as u16;
|
|
match tcp_socket.connect((address, remote_port), local_port) {
|
|
Ok(_) => {
|
|
let connection = TcpConnection {
|
|
remote: address,
|
|
remote_port,
|
|
local_port,
|
|
};
|
|
let handle = sockets.add(tcp_socket);
|
|
let sid = tcpspec.cb_sid;
|
|
let cid = xous::connect(SID::from_array(sid)).unwrap();
|
|
let tcp_cb_state = TcpState {
|
|
handle,
|
|
cid,
|
|
shutdown_rx: false,
|
|
};
|
|
tcp_handles.insert(connection, tcp_cb_state);
|
|
tcpspec.local_port = Some(local_port);
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
Err(e) => {
|
|
match e {
|
|
smoltcp::Error::Illegal => {
|
|
tcpspec.result = Some(NetMemResponse::SocketInUse);
|
|
}
|
|
smoltcp::Error::Unaddressable => {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
_ => {
|
|
tcpspec.result = Some(NetMemResponse::LibraryError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
buf.replace(tcpspec).unwrap();
|
|
},
|
|
Some(Opcode::TcpManage) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcpspec = buf.to_original::<NetTcpManage, _>().unwrap();
|
|
if let Some(local_port) = tcpspec.local_port {
|
|
let connection = TcpConnection {
|
|
remote: IpAddress::from(tcpspec.ip_addr),
|
|
remote_port: tcpspec.remote_port,
|
|
local_port,
|
|
};
|
|
if let Some(tcp_state) = tcp_handles.get_mut(&connection) {
|
|
if let Some(code) = tcpspec.mgmt_code {
|
|
match code {
|
|
TcpMgmtCode::SetRxShutdown => {
|
|
tcp_state.shutdown_rx = true;
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::SetNoDelay(value) => {
|
|
let mut socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
socket.set_nagle_enabled(value);
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::GetNoDelay(_) => {
|
|
let socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
let value = socket.nagle_enabled().is_some();
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::GetNoDelay(value));
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::SetTtl(mut ttl) => {
|
|
let mut socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
if ttl > 255 { ttl = 255; }
|
|
if ttl > 0 {
|
|
socket.set_hop_limit(Some(ttl as u8));
|
|
} else {
|
|
socket.set_hop_limit(None);
|
|
}
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::GetTtl(_) => {
|
|
let socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
let value = socket.hop_limit().unwrap_or(64); // because that's what smoltcp does
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::GetTtl(value as u32));
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::ErrorCheck(_) => {
|
|
/* // I don't think there is actually any error check reporting available? `recv_error_check()` is a private function for smoltcp...
|
|
let socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::ErrorCheck(
|
|
match socket.recv_error_check() {
|
|
Ok(_) => NetMemResponse::Ok,
|
|
Err(smoltcp::Error::Finished) => NetMemResponse::Finished,
|
|
Err(smoltcp::Error::Illegal) => NetMemResponse::Invalid,
|
|
_ => Some(NetMemResponse::LibraryError),
|
|
}
|
|
));*/
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::ErrorCheck(NetMemResponse::Ok));
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
TcpMgmtCode::Flush(_) => {
|
|
let socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
if socket.send_queue() == 0 {
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::Flush(true));
|
|
} else {
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::Flush(false));
|
|
}
|
|
}
|
|
TcpMgmtCode::CloseListener => {
|
|
// this is used for listener management, should never be sent to this routine
|
|
tcpspec.result = Some(NetMemResponse::LibraryError);
|
|
}
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
buf.replace(tcpspec).unwrap();
|
|
}
|
|
Some(Opcode::TcpTx) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcp_tx = buf.to_original::<NetTcpTransmit, _>().unwrap();
|
|
let connection = TcpConnection {
|
|
remote: IpAddress::from(tcp_tx.remote_addr),
|
|
remote_port: tcp_tx.remote_port,
|
|
local_port: tcp_tx.local_port,
|
|
};
|
|
if let Some(tcp_state) = tcp_handles.get(&connection) {
|
|
let mut socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
if socket.may_send() {
|
|
tcp_tx.result = match socket.send_slice(&tcp_tx.data[..tcp_tx.len as usize]) {
|
|
Ok(octets) => {
|
|
log::trace!("sent {}", octets);
|
|
tcp_tx.len = octets as u16;
|
|
Some(NetMemResponse::Sent(octets as u16))
|
|
},
|
|
Err(_) => Some(NetMemResponse::LibraryError),
|
|
}
|
|
} else {
|
|
log::trace!("tx can't send, please retry");
|
|
// inform the sender it should retry
|
|
tcp_tx.result = Some(NetMemResponse::SocketInUse);
|
|
}
|
|
} else {
|
|
log::trace!("tx spec invalid");
|
|
tcp_tx.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
buf.replace(tcp_tx).unwrap();
|
|
},
|
|
Some(Opcode::TcpClose) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcpspec = buf.to_original::<NetTcpManage, _>().unwrap(); // need to define this
|
|
if let Some(local_port) = tcpspec.local_port {
|
|
let connection = TcpConnection {
|
|
remote: IpAddress::from(tcpspec.ip_addr),
|
|
remote_port: tcpspec.remote_port,
|
|
local_port,
|
|
};
|
|
if let Some(tcp_state) = tcp_handles.remove(&connection) {
|
|
sockets.get::<TcpSocket>(tcp_state.handle).close();
|
|
sockets.remove(tcp_state.handle);
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
buf.replace(tcpspec).unwrap();
|
|
}
|
|
Some(Opcode::TcpListen) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcpspec = buf.to_original::<NetTcpListen, _>().unwrap();
|
|
|
|
let tcp_rx_buffer = TcpSocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
|
|
let tcp_tx_buffer = TcpSocketBuffer::new(vec![0; TCP_BUFFER_SIZE]);
|
|
let mut tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
|
|
log::debug!("adding listener to local port {}", tcpspec.local_port);
|
|
match tcp_socket.listen(tcpspec.local_port) {
|
|
Ok(_) => {
|
|
if let Some(list) = tcp_listeners.get(&tcpspec.local_port) {
|
|
if list.len() > 0 { // empty check necessary because an empty vector is left if a socket went from listening->active, or was dropped
|
|
// guarantee that all TTLs are same even if we're inserting a new socket
|
|
let ttl = sockets.get::<TcpSocket>(list[0].handle).hop_limit();
|
|
tcp_socket.set_hop_limit(ttl);
|
|
}
|
|
}
|
|
let handle = sockets.add(tcp_socket);
|
|
let sid = tcpspec.cb_sid;
|
|
let cid = xous::connect(SID::from_array(sid)).unwrap();
|
|
log::trace!("Listener with cid {}, sid {:x?} registered", cid, sid);
|
|
let tcp_cb_state = TcpState {
|
|
handle,
|
|
cid,
|
|
shutdown_rx: false,
|
|
};
|
|
if let Some(list) = tcp_listeners.get_mut(&tcpspec.local_port) {
|
|
list.push(tcp_cb_state);
|
|
log::trace!("total listeners on port {}: {}", tcpspec.local_port, list.len());
|
|
} else {
|
|
tcp_listeners.insert(tcpspec.local_port, vec![tcp_cb_state]);
|
|
log::trace!("creating first listener on port {}", tcpspec.local_port);
|
|
}
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
}
|
|
Err(e) => {
|
|
match e {
|
|
smoltcp::Error::Illegal => {
|
|
tcpspec.result = Some(NetMemResponse::SocketInUse);
|
|
}
|
|
smoltcp::Error::Unaddressable => {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
_ => {
|
|
tcpspec.result = Some(NetMemResponse::LibraryError);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
buf.replace(tcpspec).unwrap();
|
|
},
|
|
Some(Opcode::TcpManageListener) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let mut tcpspec = buf.to_original::<NetTcpManage, _>().unwrap();
|
|
match tcpspec.mgmt_code {
|
|
Some(TcpMgmtCode::CloseListener) => {
|
|
if let Some(listener) = tcp_listeners.get_mut(&tcpspec.local_port.unwrap()) {
|
|
if let Some(tcp_state) = listener.pop() {
|
|
sockets.get::<TcpSocket>(tcp_state.handle).close();
|
|
log::debug!("closing one listener on port {:?}", tcpspec.local_port);
|
|
sockets.remove(tcp_state.handle);
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
// this may leave an empty vector in the tcp_listeners structure, but I think that's OK
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
}
|
|
Some(TcpMgmtCode::SetTtl(mut ttl)) => {
|
|
if let Some(listener_vec) = tcp_listeners.get_mut(&tcpspec.local_port.unwrap()) {
|
|
for listener in listener_vec.iter_mut() {
|
|
let mut socket = sockets.get::<TcpSocket>(listener.handle);
|
|
if ttl > 255 { ttl = 255; }
|
|
if ttl > 0 {
|
|
socket.set_hop_limit(Some(ttl as u8));
|
|
} else {
|
|
socket.set_hop_limit(None);
|
|
}
|
|
}
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
}
|
|
Some(TcpMgmtCode::GetTtl(_)) => {
|
|
if let Some(listener) = tcp_listeners.get(&tcpspec.local_port.unwrap()) {
|
|
if listener.len() > 0 {
|
|
// all listeners "should" have an identical setting, so, just return the setting of the 0th one
|
|
let socket = sockets.get::<TcpSocket>(listener[0].handle);
|
|
let value = socket.hop_limit().unwrap_or(64); // because that's what smoltcp does
|
|
tcpspec.mgmt_code = Some(TcpMgmtCode::GetTtl(value as u32));
|
|
tcpspec.result = Some(NetMemResponse::Ok);
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
} else {
|
|
tcpspec.result = Some(NetMemResponse::Invalid);
|
|
}
|
|
}
|
|
_ => tcpspec.result = Some(NetMemResponse::LibraryError),
|
|
}
|
|
buf.replace(tcpspec).unwrap();
|
|
}
|
|
Some(Opcode::UdpBind) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let udpspec = buf.to_original::<NetUdpBind, _>().unwrap();
|
|
|
|
let buflen = if let Some(maxlen) = udpspec.max_payload {
|
|
maxlen as usize
|
|
} else {
|
|
NET_MTU as usize
|
|
};
|
|
if udp_handles.contains_key(&udpspec.port) {
|
|
// if we're already connected, just register the extra listener in the clones array
|
|
let sid = udpspec.cb_sid;
|
|
let cid = xous::connect(SID::from_array(sid)).unwrap();
|
|
if let Some(clone_map) = udp_clones.get_mut(&udpspec.port) {
|
|
// if a clone already exists, put the additional clone into the map
|
|
match clone_map.insert(sid, cid) {
|
|
Some(_) => {
|
|
log::error!("Something went wrong in a UDP clone operation -- same SID registered twice");
|
|
buf.replace(NetMemResponse::SocketInUse).unwrap()
|
|
}, // the same SID has double-registered, this is an error
|
|
None => buf.replace(NetMemResponse::Ok).unwrap()
|
|
}
|
|
} else {
|
|
// otherwise, create the clone mapping entry
|
|
let mut newmap = HashMap::new();
|
|
newmap.insert(sid, cid);
|
|
udp_clones.insert(
|
|
udpspec.port,
|
|
newmap
|
|
);
|
|
}
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
} else {
|
|
let udp_rx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; buflen]);
|
|
let udp_tx_buffer = UdpSocketBuffer::new(vec![UdpPacketMetadata::EMPTY], vec![0; buflen]);
|
|
let mut udp_socket = UdpSocket::new(udp_rx_buffer, udp_tx_buffer);
|
|
match udp_socket.bind(udpspec.port) {
|
|
Ok(_) => {
|
|
let sid = SID::from_array(udpspec.cb_sid);
|
|
let udpstate = UdpState {
|
|
handle: sockets.add(udp_socket),
|
|
cid: xous::connect(sid).unwrap(),
|
|
sid
|
|
};
|
|
udp_handles.insert(udpspec.port, udpstate);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
Err(e) => {
|
|
log::error!("Udp couldn't bind to socket: {:?}", e);
|
|
buf.replace(NetMemResponse::Invalid).unwrap();
|
|
}
|
|
}
|
|
}
|
|
},
|
|
Some(Opcode::UdpClose) => {
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let udpspec = buf.to_original::<NetUdpBind, _>().unwrap();
|
|
// need to find the SID that matches either in the clone array, or the primary binding.
|
|
// first check the clone array, then fall back to the primary binding
|
|
match udp_clones.get_mut(&udpspec.port) {
|
|
Some(clone_map) => {
|
|
match clone_map.remove(&udpspec.cb_sid) {
|
|
Some(cid) => {
|
|
cid_to_disconnect = Some(cid);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
continue;
|
|
}
|
|
None => {}
|
|
}
|
|
}
|
|
None => {}
|
|
}
|
|
match udp_handles.remove(&udpspec.port) {
|
|
Some(udpstate) => {
|
|
if udpstate.sid == SID::from_array(udpspec.cb_sid) {
|
|
match udp_clones.get_mut(&udpspec.port) {
|
|
// if the clone map is nil, close the socket, we're done
|
|
None => {
|
|
sockets.get::<UdpSocket>(udpstate.handle).close();
|
|
sockets.remove(udpstate.handle);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
// if the clone map has entries, promote an arbitrary map entry to the primary handle
|
|
Some(clone_map) => {
|
|
if clone_map.len() == 0 {
|
|
// removing SIDs doesn't remove the map, so it's possible to have an empty mapping. Get rid of it, and we're done.
|
|
udp_clones.remove(&udpspec.port);
|
|
sockets.get::<UdpSocket>(udpstate.handle).close();
|
|
sockets.remove(udpstate.handle);
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
} else {
|
|
// take an arbitrary key, re-insert it into the handles map.
|
|
let new_primary_sid = *clone_map.keys().next().unwrap(); // unwrap is appropriate because len already checked as not 0
|
|
let udpstate = UdpState {
|
|
handle: udpstate.handle,
|
|
cid: *clone_map.get(&new_primary_sid).unwrap(),
|
|
sid: SID::from_array(new_primary_sid),
|
|
};
|
|
udp_handles.insert(udpspec.port, udpstate);
|
|
// now remove it from the clone map
|
|
clone_map.remove(&new_primary_sid);
|
|
// clean up the clone map if it's empty
|
|
if clone_map.len() == 0 {
|
|
udp_clones.remove(&udpspec.port);
|
|
}
|
|
buf.replace(NetMemResponse::Ok).unwrap();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
buf.replace(NetMemResponse::Invalid).unwrap()
|
|
}
|
|
}
|
|
},
|
|
Some(Opcode::UdpTx) => {
|
|
use std::convert::TryInto;
|
|
let mut buf = unsafe{Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())};
|
|
let udp_tx = buf.to_original::<NetUdpTransmit, _>().unwrap();
|
|
match udp_handles.get_mut(&udp_tx.local_port) {
|
|
Some(udpstate) => {
|
|
if let Some(dest_socket) = udp_tx.dest_socket {
|
|
let endpoint = IpEndpoint::new(
|
|
dest_socket.addr.try_into().unwrap(),
|
|
dest_socket.port
|
|
);
|
|
let mut socket = sockets.get::<UdpSocket>(udpstate.handle);
|
|
match socket.send_slice(&udp_tx.data[..udp_tx.len as usize], endpoint) {
|
|
Ok(_) => buf.replace(NetMemResponse::Sent(udp_tx.len)).unwrap(),
|
|
_ => buf.replace(NetMemResponse::LibraryError).unwrap(),
|
|
}
|
|
// fire off a Pump to get the stack to actually transmit the ping; the send call merely queues it for sending
|
|
xous::try_send_message(net_conn,
|
|
Message::new_scalar(
|
|
Opcode::NetPump.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).ok();
|
|
} else {
|
|
buf.replace(NetMemResponse::Invalid).unwrap()
|
|
}
|
|
}
|
|
_ => buf.replace(NetMemResponse::Invalid).unwrap()
|
|
}
|
|
},
|
|
Some(Opcode::UdpSetTtl) => msg_scalar_unpack!(msg, ttl, port, _, _, {
|
|
match udp_handles.get_mut(&(port as u16)) {
|
|
Some(udpstate) => {
|
|
let mut socket = sockets.get::<UdpSocket>(udpstate.handle);
|
|
let checked_ttl = if ttl > 255 || ttl == 0 {
|
|
64
|
|
} else {
|
|
ttl as u8
|
|
};
|
|
socket.set_hop_limit(Some(checked_ttl));
|
|
}
|
|
None => {
|
|
log::error!("Set TTL message received, but no port was bound! port {} ttl {}", port, ttl);
|
|
}
|
|
}
|
|
}),
|
|
Some(Opcode::UdpGetTtl) => msg_blocking_scalar_unpack!(msg, port, _, _, _, {
|
|
match udp_handles.get_mut(&(port as u16)) {
|
|
Some(udpstate) => {
|
|
let socket = sockets.get::<UdpSocket>(udpstate.handle);
|
|
let ttl = socket.hop_limit().unwrap_or(64); // 64 is the value used by smoltcp if hop limit isn't set
|
|
xous::return_scalar(msg.sender, ttl as usize).expect("couldn't return TTL");
|
|
}
|
|
None => {
|
|
log::error!("Set TTL message received, but no port was bound! port {}", port);
|
|
xous::return_scalar(msg.sender, usize::MAX).expect("couldn't return TTL");
|
|
}
|
|
}
|
|
}),
|
|
|
|
Some(Opcode::ComInterrupt) => {
|
|
com_int_list.clear();
|
|
match com.ints_get_active(&mut com_int_list) {
|
|
Ok((maybe_rxlen, ints, raw_rxlen)) => {
|
|
log::debug!("COM got interrupts: {:?}, {:?}", com_int_list, maybe_rxlen);
|
|
// forward the interrupt to the connection manager as well
|
|
match xous::try_send_message(cm_cid, Message::new_scalar(
|
|
connection_manager::ConnectionManagerOpcode::ComInt.to_usize().unwrap(),
|
|
ints,
|
|
raw_rxlen,
|
|
0, 0
|
|
)) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Our net queue runneth over, interrupts were dropped.");
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error forwarding ComInt to the connection manager: {:?}", e);
|
|
}
|
|
};
|
|
for &pending in com_int_list.iter() {
|
|
if pending == ComIntSources::Invalid {
|
|
log::error!("COM interrupt vector had an error, ignoring event.");
|
|
continue;
|
|
}
|
|
}
|
|
for &pending in com_int_list.iter() {
|
|
match pending {
|
|
ComIntSources::BatteryCritical => {
|
|
log::warn!("Battery is critical! TODO: go into SHIP mode");
|
|
},
|
|
ComIntSources::WlanIpConfigUpdate => {
|
|
// right now the WLAN implementation only does IPV4. So IPV6 compatibility ends here.
|
|
// if IPV6 gets added to the EC/COM bus, ideally this is one of a couple spots in Xous that needs a tweak.
|
|
let config = com.wlan_get_config().expect("couldn't retrieve updated ipv4 config");
|
|
log::info!("Network config acquired: {:?}", config);
|
|
net_config = Some(config);
|
|
let mac = EthernetAddress::from_bytes(&config.mac);
|
|
|
|
// we need to clear the ARP cache in case we've migrated base stations (e.g. in a wireless network
|
|
// that is coverd by multiple AP), as the host AP's MAC address would have changed, and we wouldn't
|
|
// be able to route responses back. I can't seem to find a function in smoltcp 0.7.5 that allows us
|
|
// to neatly clear the ARP cache as the BTreeMap that underlies it is moved into the container and
|
|
// no "clear" API is exposed, so let's just rebuild the whole interface if we get a DHCP renewal.
|
|
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
|
let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
|
|
let routes = Routes::new(BTreeMap::new());
|
|
let device = device::NetPhy::new(&xns);
|
|
let medium = device.capabilities().medium;
|
|
let mut builder = InterfaceBuilder::new(device)
|
|
.ip_addrs(ip_addrs)
|
|
.routes(routes);
|
|
if medium == Medium::Ethernet {
|
|
builder = builder
|
|
.ethernet_addr(mac)
|
|
.neighbor_cache(neighbor_cache);
|
|
}
|
|
iface = builder.finalize();
|
|
|
|
let ip_addr =
|
|
Ipv4Cidr::new(Ipv4Address::new(
|
|
config.addr[0],
|
|
config.addr[1],
|
|
config.addr[2],
|
|
config.addr[3],
|
|
), 24);
|
|
set_ipv4_addr(&mut iface, ip_addr);
|
|
let default_v4_gw = Ipv4Address::new(
|
|
config.gtwy[0],
|
|
config.gtwy[1],
|
|
config.gtwy[2],
|
|
config.gtwy[3],
|
|
);
|
|
|
|
// reset the default route, in case it has changed
|
|
iface.routes_mut().remove_default_ipv4_route();
|
|
match iface.routes_mut().add_default_ipv4_route(default_v4_gw) {
|
|
Ok(route) => log::info!("routing table updated successfully [{:?}]", route),
|
|
Err(e) => log::error!("routing table update error: {}", e),
|
|
}
|
|
dns_allclear_hook.notify();
|
|
dns_ipv4_hook.notify_custom_args([
|
|
Some(u32::from_be_bytes(config.dns1)),
|
|
None, None, None,
|
|
]);
|
|
// the current implementation always returns 0.0.0.0 as the second dns,
|
|
// ignore this if that's what we've got; otherwise, pass it on.
|
|
if config.dns2 != [0, 0, 0, 0] {
|
|
dns_ipv4_hook.notify_custom_args([
|
|
Some(u32::from_be_bytes(config.dns2)),
|
|
None, None, None,
|
|
]);
|
|
}
|
|
},
|
|
ComIntSources::WlanRxReady => {
|
|
activity_interval.store(0, Ordering::Relaxed); // reset the activity interval to 0
|
|
if let Some(_config) = net_config {
|
|
if let Some(rxlen) = maybe_rxlen {
|
|
match iface.device_mut().push_rx_avail(rxlen) {
|
|
None => {} //log::info!("pushed {} bytes avail to iface", rxlen),
|
|
Some(_) => log::warn!("Got more packets, but smoltcp didn't drain them in time"),
|
|
}
|
|
match xous::try_send_message(
|
|
net_conn,
|
|
Message::new_scalar(Opcode::NetPump.to_usize().unwrap(), 0, 0, 0, 0)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Our net queue runneth over, packets will be dropped.");
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error sending NetPump to self: {:?}", e);
|
|
}
|
|
}
|
|
} else {
|
|
log::error!("Got RxReady interrupt but no packet length specified!");
|
|
}
|
|
}
|
|
},
|
|
_ => {
|
|
log::debug!("Unhandled: {:?}", pending);
|
|
}
|
|
}
|
|
}
|
|
com.ints_ack(&com_int_list);
|
|
},
|
|
Err(xous::Error::Timeout) => {
|
|
log::warn!("Interrupt fetch from COM timed out.");
|
|
// bread crumb: this is a "normal" error to throw when the EC is being reset,
|
|
// or when it is handling the reset of the wifi subsystem, so it's not fatal.
|
|
// However, if we see this repeatedly, it might be a good idea to add some sort
|
|
// of event counter to log the number of times we've seen this consecutively and
|
|
// if it's too much, issue a reset to the EC.
|
|
|
|
// refresh the interrupt list to the EC, just in case it lost the prior list
|
|
timer.sleep_ms(1000).unwrap(); // a brief delay because if the EC wasn't responding before, it probably needs /some/ time before being able to handle this next message
|
|
set_com_ints(&mut com_int_list);
|
|
com.ints_enable(&com_int_list);
|
|
com_int_list.clear();
|
|
}
|
|
_ => {
|
|
// not fatal, just report it.
|
|
log::error!("Unhanlded error in COM interrupt fetch");
|
|
}
|
|
}
|
|
}
|
|
Some(Opcode::NetPump) => {
|
|
let timestamp = Instant::from_millis(timer.elapsed_ms() as i64);
|
|
match iface.poll(&mut sockets, timestamp) {
|
|
Ok(_) => { }
|
|
Err(e) => {
|
|
log::debug!("poll error: {}", e);
|
|
}
|
|
}
|
|
|
|
// this block handles TCP rx
|
|
{
|
|
for (_connection, tcp_state) in tcp_handles.iter() {
|
|
if !tcp_state.shutdown_rx {
|
|
let mut socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
if socket.can_recv() {
|
|
match socket
|
|
.recv(|data| {
|
|
let mut response = NetTcpResponse {
|
|
len: data.len() as u16,
|
|
data: [0; TCP_BUFFER_SIZE],
|
|
};
|
|
for(&src, dst) in data.iter().zip(response.data.iter_mut()) {
|
|
*dst = src;
|
|
}
|
|
let buf = Buffer::into_buf(response).expect("couldn't convert TCP response to memory message");
|
|
buf.send(tcp_state.cid, NetTcpCallback::RxData.to_u32().unwrap()).expect("couldn't send TCP response");
|
|
(data.len(), ())
|
|
}) {
|
|
Ok(_) => (),
|
|
Err(e) => match e {
|
|
smoltcp::Error::Illegal => {
|
|
log::warn!("TCP fast open not supported");
|
|
},
|
|
smoltcp::Error::Finished => {
|
|
log::warn!("TCP packet received after close");
|
|
},
|
|
_ => {
|
|
log::warn!("Unknown TCP error");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// this block handles TCP listeners
|
|
// There is no lock on sending Rx messages to the listener as it is being transformed
|
|
// because all messages come from *this thread* and by definition it is not re-entrant.
|
|
// If you end up splitting the Tcp Rx task and the Tcp Listener tasks into separate threads,
|
|
// Then you must worry about the possibility of a race condition between the transition of a
|
|
// TcpListener to a regular TcpStream client (that is, an Rx packet forwarded before the
|
|
// TcpStream is fully built).
|
|
if tcp_listeners.len() > 0 { // skip the whole chunk if we don't have any tcp listeners
|
|
log::trace!("checking {} TCP listener sockets", tcp_listeners.len());
|
|
for (&local_port, tcp_state_vec) in tcp_listeners.iter_mut() {
|
|
let mut remove_indices = Vec::<usize>::new();
|
|
for (index, tcp_state) in tcp_state_vec.iter().enumerate() {
|
|
let socket = sockets.get::<TcpSocket>(tcp_state.handle);
|
|
if socket.is_active() {
|
|
log::info!("Promoting a Listener on port {:?} from {:?} to a Stream", socket.local_endpoint(), socket.remote_endpoint());
|
|
// 1. promote this socket to a TCP rx socket (this creates a double-entry that is cleaned up in step 3)
|
|
let connection = TcpConnection {
|
|
remote: socket.remote_endpoint().addr,
|
|
remote_port: socket.remote_endpoint().port,
|
|
local_port,
|
|
};
|
|
tcp_handles.insert(connection, *tcp_state);
|
|
|
|
// 2. notify the listener
|
|
let note = NetTcpListenCallback {
|
|
ip_addr: NetIpAddr::from(socket.remote_endpoint().addr),
|
|
remote_port: socket.remote_endpoint().port,
|
|
local_port,
|
|
};
|
|
log::debug!("Listener active, notification sent to {}: {:x?}", tcp_state.cid, note);
|
|
let buf = Buffer::into_buf(note).expect("can't transform memory message");
|
|
buf.send(tcp_state.cid, NetTcpCallback::ListenerActive.to_u32().unwrap()).expect("can't inform callback of active status");
|
|
log::trace!("listener index {} to remove", index);
|
|
remove_indices.push(index);
|
|
break;
|
|
} else {
|
|
log::trace!("socket not active: {:?}", socket.remote_endpoint());
|
|
}
|
|
}
|
|
// 3. now cleanup and remove the ports from the listeners status
|
|
remove_indices.sort(); // this should be in ascending order, but let's do this just to be sure.
|
|
remove_indices.reverse();
|
|
for index in remove_indices {
|
|
tcp_state_vec.remove(index);
|
|
log::trace!("removing listener index {}", index);
|
|
}
|
|
log::trace!("listeners remaining: {}", tcp_state_vec.len());
|
|
// 4. At this point, the Listener is no more. However, the caller side will "renew" the
|
|
// listener if the TcpListener is being used in an `incoming` loop.
|
|
|
|
// 5. Side note: an empty vector could be left in the tcp_listeners array. 🔥This is fine🔥.
|
|
}
|
|
}
|
|
|
|
// this block handles UDP
|
|
{
|
|
for (port, udpstate) in udp_handles.iter() {
|
|
let handle = udpstate.handle;
|
|
let mut socket = sockets.get::<UdpSocket>(handle);
|
|
match socket.recv() {
|
|
Ok((data, endpoint)) => {
|
|
log::trace!(
|
|
"udp:{} recv data: {:x?} from {}",
|
|
port,
|
|
data,
|
|
endpoint
|
|
);
|
|
// return the data/endpoint tuple to the caller
|
|
let mut response = NetUdpResponse {
|
|
endpoint_ip_addr: NetIpAddr::from(endpoint.addr),
|
|
len: data.len() as u16,
|
|
endpoint_port: endpoint.port,
|
|
data: [0; UDP_RESPONSE_MAX_LEN],
|
|
};
|
|
for (&src, dst) in data.iter().zip(response.data.iter_mut()) {
|
|
*dst = src;
|
|
}
|
|
let buf = Buffer::into_buf(response).expect("couldn't convert UDP response to memory message");
|
|
buf.send(udpstate.cid, NetUdpCallback::RxData.to_u32().unwrap()).expect("couldn't send UDP response");
|
|
// now send copies to the cloned receiver array, if they exist
|
|
if let Some(clone_map) = udp_clones.get(port) {
|
|
for &cids in clone_map.values() {
|
|
let buf = Buffer::into_buf(response).expect("couldn't convert UDP response to memory message");
|
|
buf.send(cids, NetUdpCallback::RxData.to_u32().unwrap()).expect("couldn't send UDP response");
|
|
}
|
|
}
|
|
}
|
|
Err(_) => {
|
|
// do nothing
|
|
},
|
|
};
|
|
}
|
|
}
|
|
|
|
// this block contains the ICMP Rx handler. Tx is initiated by an incoming message to the Net crate.
|
|
{
|
|
let mut socket = sockets.get::<IcmpSocket>(icmp_handle);
|
|
if !socket.is_open() {
|
|
log::error!("ICMP socket isn't open, something went wrong...");
|
|
}
|
|
|
|
if socket.can_recv() {
|
|
let (payload, _) = socket.recv().expect("couldn't receive on socket despite asserting availability");
|
|
log::trace!("icmp payload: {:x?}", payload);
|
|
let now = timer.elapsed_ms();
|
|
|
|
for (connection, waiting_queue) in ping_destinations.iter_mut() {
|
|
let remote_addr = connection.remote;
|
|
match remote_addr {
|
|
IpAddress::Ipv4(_) => {
|
|
let icmp_packet = Icmpv4Packet::new_checked(&payload).unwrap();
|
|
let icmp_repr =
|
|
Icmpv4Repr::parse(&icmp_packet, &device_caps.checksum).unwrap();
|
|
if let Icmpv4Repr::EchoReply { seq_no, data, .. } = icmp_repr {
|
|
log::trace!("got icmp seq no {} / data: {:x?}", seq_no, data);
|
|
if let Some(_) = waiting_queue.get(&seq_no) {
|
|
let packet_timestamp_ms = NetworkEndian::read_i64(data);
|
|
waiting_queue.remove(&seq_no);
|
|
// use try_send_message because we don't want to block if the recipient's queue is full;
|
|
// instead, the message is just dropped
|
|
match xous::try_send_message(connection.cid,
|
|
Message::new_scalar(
|
|
connection.retop,
|
|
NetPingCallback::NoErr.to_usize().unwrap(),
|
|
u32::from_be_bytes(remote_addr.as_bytes().try_into().unwrap()) as usize,
|
|
seq_no as usize,
|
|
(now as i64 - packet_timestamp_ms) as usize,
|
|
)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Got seq {} response, but upstream server queue is full; dropping.", &seq_no);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error: {:?}; ignoring", e);
|
|
}
|
|
}
|
|
}
|
|
} else if let Icmpv4Repr::DstUnreachable { reason, header, .. } = icmp_repr {
|
|
log::warn!("Got dst unreachable {:?}: {:?}", header.dst_addr, reason);
|
|
let reason_code: u8 = From::from(reason);
|
|
match xous::try_send_message(connection.cid,
|
|
Message::new_scalar(
|
|
connection.retop,
|
|
NetPingCallback::Unreachable.to_usize().unwrap() | (reason_code as usize) << 24,
|
|
u32::from_be_bytes(remote_addr.as_bytes().try_into().unwrap()) as usize,
|
|
0,
|
|
0,
|
|
)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Got dst {:?} unreachable, but upstream server queue is full; dropping.", remote_addr);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error: {:?}; ignoring", e);
|
|
}
|
|
}
|
|
} else {
|
|
log::error!("got unhandled ICMP type, ignoring!");
|
|
}
|
|
}
|
|
|
|
IpAddress::Ipv6(_) => {
|
|
// NOTE: actually not sure what src_ipv6 should be. This is just from an example.
|
|
let src_ipv6 = IpAddress::v6(0xfdaa, 0, 0, 0, 0, 0, 0, 1);
|
|
let icmp_packet = Icmpv6Packet::new_checked(&payload).unwrap();
|
|
let icmp_repr = Icmpv6Repr::parse(
|
|
&remote_addr,
|
|
&src_ipv6,
|
|
&icmp_packet,
|
|
&device_caps.checksum,
|
|
)
|
|
.unwrap();
|
|
let ra = remote_addr.as_bytes();
|
|
if let Icmpv6Repr::EchoReply { seq_no, data, .. } = icmp_repr {
|
|
if let Some(_) = waiting_queue.get(&seq_no) {
|
|
let packet_timestamp_ms = NetworkEndian::read_i64(data);
|
|
waiting_queue.remove(&seq_no);
|
|
match xous::try_send_message(connection.cid,
|
|
Message::new_scalar(
|
|
connection.retop,
|
|
NetPingCallback::NoErr.to_usize().unwrap(),
|
|
u32::from_be_bytes(ra[..4].try_into().unwrap()) as usize,
|
|
u32::from_be_bytes(ra[12..].try_into().unwrap()) as usize,
|
|
(now as i64 - packet_timestamp_ms) as usize,
|
|
)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Got seq {} response, but upstream server queue is full; dropping.", &seq_no);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error: {:?}; ignoring", e);
|
|
}
|
|
}
|
|
}
|
|
} else if let Icmpv6Repr::DstUnreachable { reason, header, .. } = icmp_repr {
|
|
let reason_code: u8 = From::from(reason);
|
|
log::warn!("Got dst unreachable {:?}: {:?}", header.dst_addr, reason);
|
|
match xous::try_send_message(connection.cid,
|
|
Message::new_scalar(
|
|
connection.retop,
|
|
NetPingCallback::Unreachable.to_usize().unwrap() | (reason_code as usize) << 24,
|
|
u32::from_be_bytes(ra[..4].try_into().unwrap()) as usize,
|
|
u32::from_be_bytes(ra[8..12].try_into().unwrap()) as usize,
|
|
u32::from_be_bytes(ra[12..].try_into().unwrap()) as usize,
|
|
)
|
|
){
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Got dst {:?} unreachable, but upstream server queue is full; dropping.", remote_addr);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error: {:?}; ignoring", e);
|
|
}
|
|
}
|
|
} else {
|
|
log::error!("got unhandled ICMP type, ignoring!");
|
|
}
|
|
}
|
|
_ => unimplemented!(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// this block handles ICMP retirement; it runs everytime we pump the block
|
|
{
|
|
let now = timer.elapsed_ms();
|
|
// notify the callback to drop its connection, because the queue is now empty
|
|
// do this before we clear the queue, because we want the Drop message to come on the iteration
|
|
// *after* the queue is empty.
|
|
ping_destinations.retain(|conn, v|
|
|
if v.len() == 0 {
|
|
log::debug!("Dropping ping record for {:?}", conn.remote);
|
|
let ra = conn.remote.as_bytes();
|
|
match xous::send_message(conn.cid,
|
|
Message::new_scalar( // we should wait if the queue is full, as the "Drop" message is important
|
|
conn.retop,
|
|
NetPingCallback::Drop.to_usize().unwrap(),
|
|
u32::from_be_bytes(ra[..4].try_into().unwrap()) as usize,
|
|
if ra.len() == 16 {u32::from_be_bytes(ra[12..16].try_into().unwrap()) as usize} else {0},
|
|
0,
|
|
)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerNotFound) => {
|
|
log::debug!("Server already dropped before we could send it a drop message. Ignoring.");
|
|
}
|
|
Err(e) => {
|
|
panic!("couldn't send Drop on empty queue from Ping server: {:?}", e);
|
|
}
|
|
}
|
|
match unsafe{xous::disconnect(conn.cid)} {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerNotFound) => {
|
|
log::debug!("Disconnected from a server that has already disappeared. Moving on.");
|
|
}
|
|
Err(e) => {
|
|
panic!("Unhandled error disconnecting from ping server: {:?}", e);
|
|
}
|
|
}
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
);
|
|
|
|
// now: sequence through the waiting_queue and remove entries that have hit our timeout
|
|
for (conn, waiting_queue) in ping_destinations.iter_mut() {
|
|
let ra = conn.remote.as_bytes();
|
|
waiting_queue.retain(|&seq, &mut start_time|
|
|
if now - start_time > ping_timeout_ms as u64 {
|
|
log::debug!("timeout - removing {:?}, {}", conn.remote, seq);
|
|
match xous::try_send_message(conn.cid,
|
|
Message::new_scalar( // we should wait if the queue is full, as the "Drop" message is important
|
|
conn.retop,
|
|
NetPingCallback::Timeout.to_usize().unwrap(),
|
|
u32::from_be_bytes(ra[..4].try_into().unwrap()) as usize,
|
|
seq as usize,
|
|
(now - start_time) as usize,
|
|
)
|
|
) {
|
|
Ok(_) => {},
|
|
Err(xous::Error::ServerQueueFull) => {
|
|
log::warn!("Got dst {:?} timeout, but upstream server queue is full; dropping.", conn.remote);
|
|
},
|
|
Err(xous::Error::ServerNotFound) => {
|
|
log::debug!("Callback server disappeared before we could inform it of timeout on {:?}, seq {}", conn.remote, seq);
|
|
},
|
|
Err(e) => {
|
|
log::error!("Unhandled error: {:?}; ignoring", e);
|
|
}
|
|
}
|
|
false
|
|
} else {
|
|
true
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
// establish our next check-up interval
|
|
let timestamp = Instant::from_millis(timer.elapsed_ms() as i64);
|
|
if let Some(delay) = iface.poll_delay(&sockets, timestamp) {
|
|
let delay_ms = delay.total_millis();
|
|
if delay_ms < 2 {
|
|
xous::try_send_message(net_conn,
|
|
Message::new_scalar(
|
|
Opcode::NetPump.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).ok();
|
|
} else {
|
|
if delay_threads.load(Ordering::SeqCst) < MAX_DELAY_THREADS {
|
|
let prev_count = delay_threads.fetch_add(1, Ordering::SeqCst);
|
|
log::trace!("spawning checkup thread for {}ms. New total threads: {}", delay_ms, prev_count + 1);
|
|
thread::spawn({
|
|
let parent_conn = net_conn.clone();
|
|
let delay_threads = delay_threads.clone();
|
|
move || {
|
|
let tt = ticktimer_server::Ticktimer::new().unwrap();
|
|
tt.sleep_ms(delay_ms as usize).unwrap();
|
|
xous::try_send_message(parent_conn,
|
|
Message::new_scalar(
|
|
Opcode::NetPump.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).ok();
|
|
let prev_count = delay_threads.fetch_sub(1, Ordering::SeqCst);
|
|
log::trace!("terminating checkup thread. New total threads: {}", prev_count - 1);
|
|
}
|
|
});
|
|
} else {
|
|
log::warn!("Could not queue delay of {}ms in net stack due to thread exhaustion.", delay_ms);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Some(Opcode::GetIpv4Config) => {
|
|
let mut buffer = unsafe {
|
|
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
|
|
};
|
|
let ser = if let Some(config) = net_config {
|
|
Some(config.encode_u16())
|
|
} else {
|
|
None
|
|
};
|
|
buffer.replace(ser).expect("couldn't return config");
|
|
},
|
|
Some(Opcode::SubscribeWifiStats) => {
|
|
let buffer = unsafe {
|
|
Buffer::from_memory_message(msg.body.memory_message().unwrap())
|
|
};
|
|
// have to transform it through the local memory space because you can't re-lend pages
|
|
let sub = buffer.to_original::<WifiStateSubscription, _>().unwrap();
|
|
let buf = Buffer::into_buf(sub).expect("couldn't convert to memory message");
|
|
buf.send(cm_cid, connection_manager::ConnectionManagerOpcode::SubscribeWifiStats.to_u32().unwrap()).expect("couldn't forward subscription request");
|
|
},
|
|
Some(Opcode::UnsubWifiStats) => msg_blocking_scalar_unpack!(msg, s0, s1, s2, s3, {
|
|
// now do something with the unsubscription
|
|
let response = xous::send_message(cm_cid, Message::new_blocking_scalar(
|
|
connection_manager::ConnectionManagerOpcode::UnsubWifiStats.to_usize().unwrap(),
|
|
s0, s1, s2, s3
|
|
)).expect("couldn't send unsub message");
|
|
if let xous::Result::Scalar1(val) = response {
|
|
xous::return_scalar(msg.sender, val).unwrap();
|
|
} else {
|
|
xous::return_scalar(msg.sender, 0).unwrap();
|
|
}
|
|
}),
|
|
Some(Opcode::FetchSsidList) => {
|
|
let mut buffer = unsafe {
|
|
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
|
|
};
|
|
let ret_storage = SsidList::default();
|
|
let mut buf = Buffer::into_buf(ret_storage).expect("couldn't convert to memory message");
|
|
buf.lend_mut(cm_cid, connection_manager::ConnectionManagerOpcode::FetchSsidList.to_u32().unwrap()).expect("couldn't forward ssid list request");
|
|
let ret_list = buf.to_original::<SsidList, _>().expect("couldn't restore original");
|
|
buffer.replace(ret_list).expect("couldn't return config");
|
|
},
|
|
Some(Opcode::ConnMgrStartStop) => msg_scalar_unpack!(msg, code, _, _, _, {
|
|
if code == 0 { // 0 is stop, 1 is start
|
|
send_message(cm_cid,
|
|
Message::new_scalar(
|
|
connection_manager::ConnectionManagerOpcode::Stop.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).expect("couldn't send stop message");
|
|
} else if code == 1 {
|
|
send_message(cm_cid,
|
|
Message::new_scalar(
|
|
connection_manager::ConnectionManagerOpcode::Run.to_usize().unwrap(),
|
|
0, 0, 0, 0)
|
|
).expect("couldn't send stop message");
|
|
} else {
|
|
log::error!("Got incorrect start/stop code: {}", code);
|
|
}
|
|
}),
|
|
Some(Opcode::Reset) => {
|
|
net_config = None;
|
|
let neighbor_cache = NeighborCache::new(BTreeMap::new());
|
|
let ip_addrs = [IpCidr::new(Ipv4Address::UNSPECIFIED.into(), 0)];
|
|
let routes = Routes::new(BTreeMap::new());
|
|
let device = device::NetPhy::new(&xns);
|
|
let medium = device.capabilities().medium;
|
|
let mut builder = InterfaceBuilder::new(device)
|
|
.ip_addrs(ip_addrs)
|
|
.routes(routes);
|
|
if medium == Medium::Ethernet {
|
|
builder = builder
|
|
.ethernet_addr(EthernetAddress::from_bytes(&[0; 6]))
|
|
.neighbor_cache(neighbor_cache);
|
|
}
|
|
iface = builder.finalize();
|
|
iface.routes_mut().remove_default_ipv4_route();
|
|
dns_allclear_hook.notify();
|
|
// question: do we need to clear the UDP and ICMP states?
|
|
xous::return_scalar(msg.sender, 1).unwrap();
|
|
}
|
|
Some(Opcode::SuspendResume) => xous::msg_scalar_unpack!(msg, token, _, _, _, {
|
|
com_int_list.clear();
|
|
com.ints_enable(&com_int_list); // disable all the interrupts
|
|
|
|
susres.suspend_until_resume(token).expect("couldn't execute suspend/resume");
|
|
// re-enable the interrupts
|
|
com_int_list.clear();
|
|
com_int_list.push(ComIntSources::WlanIpConfigUpdate);
|
|
com_int_list.push(ComIntSources::WlanRxReady);
|
|
com_int_list.push(ComIntSources::BatteryCritical);
|
|
com_int_list.push(ComIntSources::Connect);
|
|
com_int_list.push(ComIntSources::Disconnect);
|
|
com_int_list.push(ComIntSources::WlanSsidScanUpdate);
|
|
com_int_list.push(ComIntSources::WlanSsidScanFinished);
|
|
com_int_list.push(ComIntSources::WfxErr);
|
|
com.ints_enable(&com_int_list);
|
|
}),
|
|
Some(Opcode::Quit) => {
|
|
log::warn!("quit received");
|
|
break;
|
|
}
|
|
None => {
|
|
log::error!("couldn't convert opcode: {:?}", msg);
|
|
}
|
|
}
|
|
}
|
|
// clean up our program
|
|
log::trace!("main loop exit, destroying servers");
|
|
xous::send_message(cm_cid, Message::new_blocking_scalar(connection_manager::ConnectionManagerOpcode::Quit.to_usize().unwrap(), 0, 0, 0, 0)).expect("couldn't quit connection manager server");
|
|
unsafe{xous::disconnect(cm_cid).ok()};
|
|
xns.unregister_server(net_sid).unwrap();
|
|
xous::destroy_server(net_sid).unwrap();
|
|
log::trace!("quitting");
|
|
xous::terminate_process(0)
|
|
}
|