From 178be5ed893a191924e2935d943daf38fe807d7e Mon Sep 17 00:00:00 2001 From: Mathias Date: Wed, 29 Sep 2021 07:58:56 +0200 Subject: [PATCH] Implement TcpFullStack with server capabilities, and TcpListeners --- ublox-short-range/Cargo.toml | 2 +- ublox-short-range/src/client.rs | 185 ++++++++++++------ .../src/command/data_mode/mod.rs | 46 ++--- .../src/command/data_mode/responses.rs | 3 +- .../src/command/data_mode/types.rs | 8 +- ublox-short-range/src/wifi/tcp_stack.rs | 172 ++++++++++------ ublox-short-range/src/wifi/udp_stack.rs | 15 +- 7 files changed, 277 insertions(+), 154 deletions(-) diff --git a/ublox-short-range/Cargo.toml b/ublox-short-range/Cargo.toml index 73e3336..03be3b2 100644 --- a/ublox-short-range/Cargo.toml +++ b/ublox-short-range/Cargo.toml @@ -19,7 +19,7 @@ atat = { version = "0.13.1", features = ["derive", "defmt", "bytes"] } heapless = { version = "^0.7", features = ["serde"] } no-std-net = { version = "^0.5", features = ["serde"] } serde = { version = "^1", default-features = false, features = ["derive"] } -ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", rev = "097c2822" } +ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", rev = "f57d5f1" } hash32 = "^0.2.1" hash32-derive = "^0.1.0" diff --git a/ublox-short-range/src/client.rs b/ublox-short-range/src/client.rs index d71519e..771b7bf 100644 --- a/ublox-short-range/src/client.rs +++ b/ublox-short-range/src/client.rs @@ -1,5 +1,6 @@ use crate::{ command::{ + data_mode::types::IPProtocol, edm::{types::Protocol, urc::EdmEvent, EdmAtCmdWrapper, SwitchToEdmCommand}, ping::types::PingError, system::{ @@ -16,11 +17,15 @@ use crate::{ }, }; use core::convert::TryInto; +use core::str::FromStr; use embedded_hal::digital::OutputPin; -use embedded_nal::{nb, IpAddr, SocketAddr}; +use embedded_nal::{nb, IpAddr, Ipv4Addr, SocketAddr}; use embedded_time::duration::{Generic, Milliseconds}; use embedded_time::Clock; -use ublox_sockets::{AnySocket, SocketSet, SocketType, TcpSocket, TcpState, UdpSocket, UdpState}; +use ublox_sockets::{ + tcp_listener::TcpListener, AnySocket, SocketSet, SocketType, TcpSocket, TcpState, UdpSocket, + UdpState, +}; #[derive(PartialEq, Copy, Clone)] pub enum SerialMode { @@ -61,6 +66,7 @@ where pub(crate) timer: CLK, pub(crate) reset_pin: Option, pub(crate) edm_mapping: EdmMap, + pub(crate) tcp_listener: TcpListener<3, N>, } impl UbloxClient @@ -84,6 +90,7 @@ where timer, reset_pin, edm_mapping: EdmMap::new(), + tcp_listener: TcpListener::new(), } } @@ -213,6 +220,7 @@ where fn handle_urc(&mut self) -> Result<(), Error> { if let Some(ref mut sockets) = self.sockets.as_deref_mut() { let dns_state = &mut self.dns_state; + let tcp_listener = &mut self.tcp_listener; let edm_mapping = &mut self.edm_mapping; let wifi_connection = self.wifi_connection.as_mut(); let ts = self.timer.try_now().map_err(|_| Error::Timer)?; @@ -224,18 +232,55 @@ where let res = match edm_urc { EdmEvent::ATEvent(urc) => { match urc { - Urc::PeerConnected(_) => { - defmt::trace!("[URC] PeerConnected"); - - // TODO: - // - // We should probably move - // `tcp.set_state(TcpState::Connected(endpoint));` - // + `udp.set_state(UdpState::Established);` as - // well as `tcp.update_handle(*socket);` + - // `udp.update_handle(*socket);` here, to make - // sure that part also works without EDM mode - true + Urc::PeerConnected(event) => { + defmt::trace!( + "[URC] PeerConnected {:?}", + defmt::Debug2Format(&event) + ); + + let remote_ip = Ipv4Addr::from_str( + core::str::from_utf8(event.remote_address.as_slice()).unwrap(), + ) + .unwrap(); + + let remote = SocketAddr::new(remote_ip.into(), event.remote_port); + + if let Some(queue) = tcp_listener.incoming(event.local_port) { + queue.enqueue((event.handle, remote)).unwrap(); + return true; + } else { + match event.protocol { + IPProtocol::TCP => { + if let Ok(mut tcp) = + sockets.get::>(event.handle) + { + defmt::debug!( + "Binding remote {=[u8]:a} to TCP socket {:?}", + event.remote_address.as_slice(), + event.handle + ); + tcp.set_state(TcpState::Connected(remote)); + return true; + } + } + IPProtocol::UDP => { + if let Ok(mut udp) = + sockets.get::>(event.handle) + { + defmt::debug!( + "Binding remote {=[u8]:a} to UDP socket {:?}", + event.remote_address.as_slice(), + event.handle + ); + udp.bind(remote).unwrap(); + udp.set_state(UdpState::Established); + return true; + } + } + } + } + + false } Urc::PeerDisconnected(msg) => { defmt::trace!("[URC] PeerDisconnected"); @@ -253,6 +298,7 @@ where { udp.close(); } + // FIXME: Is this correct? sockets.remove(msg.handle).ok(); } _ => {} @@ -287,10 +333,16 @@ where } Urc::WifiAPUp(_) => { defmt::trace!("[URC] WifiAPUp"); + if let Some(con) = wifi_connection { + con.wifi_state = WiFiState::Connected; + } true } Urc::WifiAPDown(_) => { defmt::trace!("[URC] WifiAPDown"); + if let Some(con) = wifi_connection { + con.wifi_state = WiFiState::NotConnected; + } true } Urc::WifiAPStationConnected(client) => { @@ -324,7 +376,6 @@ where con.network_state = NetworkState::AlmostAttached } } - // con.network_state = NetworkState::Attached; } true } @@ -362,29 +413,48 @@ where EdmEvent::IPv4ConnectEvent(event) => { defmt::trace!( "[EDM_URC] IPv4ConnectEvent! Channel_id: {:?}", - event.channel_id + defmt::Debug2Format(&event) ); let endpoint = SocketAddr::new(event.remote_ip.into(), event.remote_port); + // Peek the queue of unaccepted requests, to attempt + // binding an EDM channel to a socket handle + if let Some(queue) = tcp_listener.incoming(event.local_port) { + match queue.peek() { + Some((h, remote)) if remote == &endpoint => { + return edm_mapping.insert(event.channel_id, *h).is_ok(); + } + _ => {} + } + } + sockets .iter_mut() .find_map(|(h, s)| { match event.protocol { Protocol::TCP => { - let mut tcp = TcpSocket::downcast(s).ok()?; + let tcp = TcpSocket::downcast(s).ok()?; if tcp.endpoint() == Some(endpoint) { + defmt::debug!( + "Binding EDM channel {} to TCP socket {}", + event.channel_id, + h + ); edm_mapping.insert(event.channel_id, h).unwrap(); - tcp.set_state(TcpState::Connected(endpoint)); - return Some(true); + return Some(()); } } Protocol::UDP => { - let mut udp = UdpSocket::downcast(s).ok()?; + let udp = UdpSocket::downcast(s).ok()?; if udp.endpoint() == Some(endpoint) { + defmt::debug!( + "Binding EDM channel {} to UDP socket {}", + event.channel_id, + h + ); edm_mapping.insert(event.channel_id, h).unwrap(); - udp.set_state(UdpState::Established); - return Some(true); + return Some(()); } } _ => {} @@ -396,36 +466,43 @@ where EdmEvent::IPv6ConnectEvent(event) => { defmt::trace!( "[EDM_URC] IPv6ConnectEvent! Channel_id: {:?}", - event.channel_id + defmt::Debug2Format(&event) ); let endpoint = SocketAddr::new(event.remote_ip.into(), event.remote_port); - sockets - .iter_mut() - .find_map(|(h, s)| { - match event.protocol { - Protocol::TCP => { - let mut tcp = TcpSocket::downcast(s).ok()?; - if tcp.endpoint() == Some(endpoint) { - edm_mapping.insert(event.channel_id, h).unwrap(); - tcp.set_state(TcpState::Connected(endpoint)); - return Some(true); + if let Some(queue) = tcp_listener.incoming(event.local_port) { + match queue.peek() { + Some((h, remote)) if remote == &endpoint => { + edm_mapping.insert(event.channel_id, *h).is_ok() + } + _ => false, + } + } else { + sockets + .iter_mut() + .find_map(|(h, s)| { + match event.protocol { + Protocol::TCP => { + let tcp = TcpSocket::downcast(s).ok()?; + if tcp.endpoint() == Some(endpoint) { + edm_mapping.insert(event.channel_id, h).unwrap(); + return Some(()); + } } - } - Protocol::UDP => { - let mut udp = UdpSocket::downcast(s).ok()?; - if udp.endpoint() == Some(endpoint) { - edm_mapping.insert(event.channel_id, h).unwrap(); - udp.set_state(UdpState::Established); - return Some(true); + Protocol::UDP => { + let udp = UdpSocket::downcast(s).ok()?; + if udp.endpoint() == Some(endpoint) { + edm_mapping.insert(event.channel_id, h).unwrap(); + return Some(()); + } } + _ => {} } - _ => {} - } - None - }) - .is_some() + None + }) + .is_some() + } } EdmEvent::BluetoothConnectEvent(_) => { defmt::trace!("[EDM_URC] BluetoothConnectEvent"); @@ -448,12 +525,9 @@ where let mut tcp = sockets .get::>(*socket_handle) .unwrap(); - if tcp.can_recv() { - tcp.rx_enqueue_slice(&event.data); - true - } else { - false - } + tcp.can_recv() + .then(|| tcp.rx_enqueue_slice(&event.data)) + .is_some() } Some(SocketType::Udp) => { // Handle udp socket @@ -461,12 +535,9 @@ where .get::>(*socket_handle) .unwrap(); - if udp.can_recv() { - udp.rx_enqueue_slice(&event.data); - true - } else { - false - } + udp.can_recv() + .then(|| udp.rx_enqueue_slice(&event.data)) + .is_some() } _ => { defmt::error!("SocketNotFound {:?}", socket_handle); diff --git a/ublox-short-range/src/command/data_mode/mod.rs b/ublox-short-range/src/command/data_mode/mod.rs index 6ee3b06..3ec5b54 100644 --- a/ublox-short-range/src/command/data_mode/mod.rs +++ b/ublox-short-range/src/command/data_mode/mod.rs @@ -12,9 +12,9 @@ use super::NoResponse; /// 5.1 Enter data mode O /// -/// Requests the module to move to the new mode. -/// After executing the data mode command or the extended data mode command, a delay of 50 ms is -/// required before start of data transmission. +/// Requests the module to move to the new mode. After executing the data mode +/// command or the extended data mode command, a delay of 50 ms is required +/// before start of data transmission. #[derive(Clone, AtatCmd)] #[at_cmd("O", NoResponse, timeout_ms = 10000, value_sep = false)] pub struct ChangeMode { @@ -24,9 +24,9 @@ pub struct ChangeMode { /// 5.2 Connect peer +UDCP /// -/// Connects to an enabled service on a remote device. When the host connects to a -/// service on a remote device, it implicitly registers to receive the "Connection Closed" -/// event. +/// Connects to an enabled service on a remote device. When the host connects to +/// a service on a remote device, it implicitly registers to receive the +/// "Connection Closed" event. #[derive(Clone, AtatCmd)] #[at_cmd("+UDCP", ConnectPeerResponse, timeout_ms = 10000)] pub struct ConnectPeer<'a> { @@ -46,9 +46,10 @@ pub struct ClosePeerConnection { /// 5.4 Default remote peer +UDDRP /// -/// The default remote peer command works for Bluetooth BR/EDR, Bluetooth low energy (SPS), TCP, and UDP. -/// The DCE will connect to a default remote peer when entering either the Data mode or Extended data mode -/// (either by command or at start up, if defined by the Module Start Mode +UMSM command). +/// The default remote peer command works for Bluetooth BR/EDR, Bluetooth low +/// energy (SPS), TCP, and UDP. The DCE will connect to a default remote peer +/// when entering either the Data mode or Extended data mode (either by command +/// or at start up, if defined by the Module Start Mode +UMSM command). #[derive(Clone, AtatCmd)] #[at_cmd("+UDDRP", NoResponse, timeout_ms = 10000)] pub struct SetDefaultRemotePeer<'a> { @@ -74,7 +75,8 @@ pub struct PeerList; #[derive(Clone, AtatCmd)] #[at_cmd("+UDSC", NoResponse, timeout_ms = 10000)] pub struct ServerConfiguration { - /// 0-6, the server ID to configure. Disable an active server first before changing. + /// 0-6, the server ID to configure. Disable an active server first before + /// changing. #[at_arg(position = 0)] pub id: u8, #[at_arg(position = 1)] @@ -83,10 +85,11 @@ pub struct ServerConfiguration { /// 5.7 Server flags +UDSF /// -/// Bit 0, remote configuration: When the remote configuration bit is set, the module will look for the escape -/// sequence over the air (see S2 command). When the escape sequence is detected, the channel will enter -/// command mode and parse AT commands. The command mode is exited by sending an ATO to the module (see -/// O command). +/// Bit 0, remote configuration: When the remote configuration bit is set, the +/// module will look for the escape sequence over the air (see S2 command). When +/// the escape sequence is detected, the channel will enter command mode and +/// parse AT commands. The command mode is exited by sending an ATO to the +/// module (see O command). #[derive(Clone, AtatCmd)] #[at_cmd("+UDSF", NoResponse, timeout_ms = 10000)] pub struct SetServerFlags { @@ -100,8 +103,8 @@ pub struct SetServerFlags { /// 5.8 Watchdog settings +UDWS /// -/// The data watchdog functionality is active only in the data or extended data mode. Additionally, the power -/// mode must also be set to online or sleep mode. +/// The data watchdog functionality is active only in the data or extended data +/// mode. Additionally, the power mode must also be set to online or sleep mode. #[derive(Clone, AtatCmd)] #[at_cmd("+UDWS", NoResponse, timeout_ms = 10000)] pub struct SetWatchdogSettings { @@ -127,9 +130,8 @@ pub struct SetPeerConfiguration { /// 5.12 Bind +UDBIND /// -/// Writes backspace character. -/// This setting changes the decimal value of the character recognized by the DCE as a -/// request to delete from the command line, the immediately preceding character. +/// Bind two streams together for transparent data transfer between physical +/// interfaces. #[derive(Clone, AtatCmd)] #[at_cmd("+UDBIND", BindResponse, timeout_ms = 10000)] pub struct SetBind { @@ -141,9 +143,9 @@ pub struct SetBind { /// 5.13 Bind to channel +UDBINDC /// -/// Binds Stream with Id to channel with Id . Stream ids are -/// provided on response of a successful connection. Channel id is provided on response -/// of a successful bind command. +/// Binds Stream with Id to channel with Id . Stream ids +/// are provided on response of a successful connection. Channel id is provided +/// on response of a successful bind command. #[derive(Clone, AtatCmd)] #[at_cmd("+UDBINDC", NoResponse, timeout_ms = 10000)] pub struct SoftwareUpdate { diff --git a/ublox-short-range/src/command/data_mode/responses.rs b/ublox-short-range/src/command/data_mode/responses.rs index 4f119c8..7e20b16 100644 --- a/ublox-short-range/src/command/data_mode/responses.rs +++ b/ublox-short-range/src/command/data_mode/responses.rs @@ -1,12 +1,13 @@ //! Responses for Data Mode use atat::atat_derive::AtatResp; use heapless::String; +use ublox_sockets::SocketHandle; /// 5.2 Connect peer +UDCP #[derive(Clone, AtatResp)] pub struct ConnectPeerResponse { #[at_arg(position = 0)] - pub peer_handle: u8, + pub peer_handle: SocketHandle, } /// 5.5 Peer list +UDLP diff --git a/ublox-short-range/src/command/data_mode/types.rs b/ublox-short-range/src/command/data_mode/types.rs index 0dc9446..9a276d2 100644 --- a/ublox-short-range/src/command/data_mode/types.rs +++ b/ublox-short-range/src/command/data_mode/types.rs @@ -44,13 +44,8 @@ pub enum ConnectScheme { } #[derive(Clone, PartialEq, AtatEnum)] pub enum ServerConfig { - Type(ServerType), - Url(String<128>), -} -#[derive(Clone, PartialEq, AtatEnum)] -pub enum ServerType { #[at_arg(value = 0)] - Dissabled, + Disabled, #[at_arg(value = 1)] TCP(u16, ImmediateFlush), #[at_arg(value = 2)] @@ -65,6 +60,7 @@ pub enum ServerType { SPS, #[at_arg(value = 8)] ATP(Interface, Option), + Url(String<128>), } #[derive(Clone, PartialEq, AtatEnum)] diff --git a/ublox-short-range/src/wifi/tcp_stack.rs b/ublox-short-range/src/wifi/tcp_stack.rs index 835fb7f..b4f6d79 100644 --- a/ublox-short-range/src/wifi/tcp_stack.rs +++ b/ublox-short-range/src/wifi/tcp_stack.rs @@ -1,7 +1,7 @@ use crate::{ command::data_mode::*, command::{ - data_mode::responses::ConnectPeerResponse, + data_mode::types::{ImmediateFlush, ServerConfig}, edm::{EdmAtCmdWrapper, EdmDataCommand}, }, wifi::peer_builder::PeerUrlBuilder, @@ -34,7 +34,11 @@ where // as the Socket object itself provides no value without accessing it though the client. type TcpSocket = SocketHandle; - /// Open a new TCP socket to the given address and port. The socket starts in the unconnected state. + /// Open a socket for usage as a TCP client. + /// + /// The socket must be connected before it can be used. + /// + /// Returns `Ok(socket)` if the socket was successfully created. fn socket(&mut self) -> Result { if let Some(ref mut sockets) = self.sockets { // Check if there are any unused sockets available @@ -75,10 +79,9 @@ where remote: SocketAddr, ) -> nb::Result<(), Self::Error> { if self.sockets.is_none() { - return Err(Error::Illegal.into()); + return Err(nb::Error::Other(Error::Illegal)); } - defmt::debug!("[TCP] Connect socket: {:?}", socket); if let Some(ref con) = self.wifi_connection { if !self.initialized || !con.is_connected() { return Err(nb::Error::Other(Error::Illegal)); @@ -88,51 +91,46 @@ where } // If no socket is found we stop here - // TODO: This could probably be done nicer? - self.sockets - .as_mut() - .unwrap() - .get::>(*socket) - .map_err(Self::Error::from)?; - - let url = PeerUrlBuilder::new() - .address(&remote) - .creds(self.security_credentials.clone()) - .tcp() - .map_err(|_| Error::Unaddressable)?; - - defmt::trace!("[TCP] Connecting to url! {=str}", url); - - let ConnectPeerResponse { peer_handle } = self - .send_internal(&EdmAtCmdWrapper(ConnectPeer { url: &url }), false) - .map_err(|_| Error::Unaddressable)?; - - let mut tcp = self + match self .sockets .as_mut() .unwrap() .get::>(*socket) - .map_err(Self::Error::from)?; + .map_err(Self::Error::from)? + .state() + { + TcpState::Created => { + let url = PeerUrlBuilder::new() + .address(&remote) + .creds(self.security_credentials.clone()) + .tcp() + .map_err(|_| Error::Unaddressable)?; + + defmt::trace!("[TCP] Connecting to url! {=str}", url); - *socket = SocketHandle(peer_handle); - tcp.set_state(TcpState::WaitingForConnect(remote)); - tcp.update_handle(*socket); + let new_handle = self + .send_at(ConnectPeer { url: &url }) + .map_err(|_| Error::Unaddressable)? + .peer_handle; - // TODO: Timeout? - while { - matches!( - self.sockets + let mut tcp = self + .sockets .as_mut() .unwrap() .get::>(*socket) - .map_err(Self::Error::from)? - .state(), - TcpState::WaitingForConnect(_) - ) - } { - self.spin().map_err(|_| Error::Illegal)?; + .map_err(Self::Error::from)?; + *socket = new_handle; + tcp.set_state(TcpState::WaitingForConnect(remote)); + tcp.update_handle(*socket); + Err(nb::Error::WouldBlock) + } + TcpState::WaitingForConnect(_) => { + self.spin().map_err(|_| Error::Illegal)?; + Err(nb::Error::WouldBlock) + } + TcpState::Connected(_) => Ok(()), + _ => Err(Error::Illegal.into()), } - Ok(()) } /// Check if this socket is still connected @@ -236,6 +234,7 @@ where sockets.remove(socket)?; } } + // TODO: Close listening socket? Ok(()) } else { Err(Error::Illegal) @@ -243,25 +242,76 @@ where } } -// impl TcpFullStack for UbloxClient -// where -// C: atat::AtatClient, -// CLK: Clock, -// RST: OutputPin, -// Generic: TryInto, -// { -// fn bind(&mut self, socket: &mut Self::TcpSocket, local_port: u16) -> Result<(), Self::Error> { -// todo!() -// } - -// fn listen(&mut self, socket: &mut Self::TcpSocket) -> Result<(), Self::Error> { -// todo!() -// } - -// fn accept( -// &mut self, -// socket: &mut Self::TcpSocket, -// ) -> nb::Result<(Self::TcpSocket, SocketAddr), Self::Error> { -// todo!() -// } -// } +impl TcpFullStack for UbloxClient +where + C: atat::AtatClient, + CLK: Clock, + RST: OutputPin, + Generic: TryInto, +{ + fn bind(&mut self, socket: &mut Self::TcpSocket, local_port: u16) -> Result<(), Self::Error> { + if self.sockets.is_none() { + return Err(Error::Illegal); + } + + defmt::debug!("[TCP] bind socket: {:?}", socket); + if let Some(ref con) = self.wifi_connection { + if !self.initialized || !con.is_connected() { + return Err(Error::Illegal); + } + } else { + return Err(Error::Illegal); + } + + self.send_internal( + &EdmAtCmdWrapper(ServerConfiguration { + id: 1, + server_config: ServerConfig::TCP(local_port, ImmediateFlush::Disable), + }), + false, + ) + .map_err(|_| Error::Unaddressable)?; + + self.tcp_listener + .bind(*socket, local_port) + .map_err(|_| Error::Illegal)?; + + Ok(()) + } + + fn listen(&mut self, _socket: &mut Self::TcpSocket) -> Result<(), Self::Error> { + Ok(()) + } + + fn accept( + &mut self, + socket: &mut Self::TcpSocket, + ) -> nb::Result<(Self::TcpSocket, SocketAddr), Self::Error> { + if !self + .tcp_listener + .available(*socket) + .map_err(|_| Error::NotBound)? + { + return Err(nb::Error::WouldBlock); + } + + let data_socket = self.socket()?; + let (handle, remote) = self + .tcp_listener + .accept(*socket) + .map_err(|_| Error::NotBound)?; + + // Crate a new SocketBuffer allocation for the incoming connection + let mut tcp = self + .sockets + .as_mut() + .ok_or(Error::Illegal)? + .get::>(data_socket) + .map_err(Self::Error::from)?; + + tcp.update_handle(handle); + tcp.set_state(TcpState::Connected(remote.clone())); + + Ok((handle, remote)) + } +} diff --git a/ublox-short-range/src/wifi/udp_stack.rs b/ublox-short-range/src/wifi/udp_stack.rs index cfd5db0..333c56f 100644 --- a/ublox-short-range/src/wifi/udp_stack.rs +++ b/ublox-short-range/src/wifi/udp_stack.rs @@ -74,7 +74,6 @@ where return Err(Error::Illegal); } - defmt::debug!("[UDP] Connecting socket"); if let Some(ref con) = self.wifi_connection { if !self.initialized || !con.is_connected() { return Err(Error::Illegal); @@ -88,19 +87,21 @@ where .udp() .map_err(|_| Error::Unaddressable)?; defmt::trace!("[UDP] Connecting URL! {=str}", url); - let resp = self + let new_handle = self .send_internal(&EdmAtCmdWrapper(ConnectPeer { url: &url }), false) - .map_err(|_| Error::Unaddressable)?; + .map_err(|_| Error::Unaddressable)? + .peer_handle; let mut udp = self .sockets .as_mut() .unwrap() .get::>(*socket)?; - *socket = SocketHandle(resp.peer_handle); - udp.bind(remote)?; + + *socket = new_handle; udp.update_handle(*socket); + // FIXME: Should this be blocking here? while self .sockets .as_mut() @@ -133,10 +134,12 @@ where return Err(Error::SocketClosed.into()); } + self.spin().map_err(|_| nb::Error::Other(Error::Illegal))?; + let channel = *self .edm_mapping .channel_id(socket) - .ok_or(nb::Error::Other(Error::SocketClosed))?; + .ok_or(nb::Error::WouldBlock)?; for chunk in buffer.chunks(EGRESS_CHUNK_SIZE) { self.send_internal(