From d998dca5fa39bf777a909573df5b5579c44a8a69 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 | 3 +- ublox-short-range/src/client.rs | 105 +++++++++--- .../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 | 162 +++++++++++------- ublox-short-range/src/wifi/udp_stack.rs | 15 +- 7 files changed, 217 insertions(+), 125 deletions(-) diff --git a/ublox-short-range/Cargo.toml b/ublox-short-range/Cargo.toml index 73e3336..884cf87 100644 --- a/ublox-short-range/Cargo.toml +++ b/ublox-short-range/Cargo.toml @@ -19,7 +19,8 @@ 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 = { path = "../../ublox-sockets" } +# ublox-sockets = { git = "https://github.com/BlackbirdHQ/ublox-sockets", rev = "097c2822" } 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..f95c5a5 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) => { @@ -362,7 +414,7 @@ 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); @@ -372,19 +424,17 @@ where .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) { 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) { edm_mapping.insert(event.channel_id, h).unwrap(); - udp.set_state(UdpState::Established); - return Some(true); + return Some(()); } } _ => {} @@ -396,7 +446,7 @@ 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); @@ -406,19 +456,18 @@ where .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) { 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()?; + defmt::debug!("Found matching UDP socket"); if udp.endpoint() == Some(endpoint) { edm_mapping.insert(event.channel_id, h).unwrap(); - udp.set_state(UdpState::Established); - return Some(true); + return Some(()); } } _ => {} 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..16ed713 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)?; - *socket = SocketHandle(peer_handle); - tcp.set_state(TcpState::WaitingForConnect(remote)); - tcp.update_handle(*socket); + defmt::trace!("[TCP] Connecting to url! {=str}", url); - // TODO: Timeout? - while { - matches!( - self.sockets + let new_handle = self + .send_at(ConnectPeer { url: &url }) + .map_err(|_| Error::Unaddressable)? + .peer_handle; + + 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 @@ -243,25 +241,67 @@ 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).unwrap(); + + 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).unwrap() { + return Err(nb::Error::WouldBlock); + } + + let data_socket = self.socket()?; + let (handle, remote) = self.tcp_listener.accept(*socket).unwrap(); + + // Crate a new SocketBuffer allocation for the incoming connection + let mut tcp = self + .sockets + .as_mut() + .unwrap() + .get::>(data_socket) + .map_err(Self::Error::from)?; + + tcp.update_handle(handle); + tcp.set_state(TcpState::Connected(remote.clone())); + + Ok((data_socket, 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(