9 changes: 9 additions & 0 deletions src/modem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//******************************************************************************

use crate::Error;
use log::debug;

//******************************************************************************
// Types
Expand Down Expand Up @@ -65,6 +66,7 @@ pub enum SystemMode {
/// The list of acceptable CEREG response indications is taken from the Nordic
/// `lte_link_control` driver.
pub fn wait_for_lte() -> Result<(), Error> {
debug!("Waiting for LTE...");
let skt = crate::at::AtSocket::new()?;
// Subscribe
skt.write(b"AT+CEREG=2")?;
Expand All @@ -77,6 +79,7 @@ pub fn wait_for_lte() -> Result<(), Error> {
let s = unsafe { core::str::from_utf8_unchecked(&buf[0..length - 1]) };
for line in s.lines() {
let line = line.trim();
debug!("RX {:?}", line);
for ind in &connected_indications {
if line.starts_with(ind) {
break 'outer;
Expand All @@ -93,18 +96,21 @@ pub fn wait_for_lte() -> Result<(), Error> {
/// Powers the modem on and sets it to auto-register, but does not wait for it
/// to connect to a network.
pub fn on() -> Result<(), Error> {
debug!("Turning modem ON");
crate::at::send_at_command("AT+CFUN=1", |_| {})?;
Ok(())
}

/// Puts the modem into flight mode.
pub fn flight_mode() -> Result<(), Error> {
debug!("Turning mode to FLIGHT MODE");
crate::at::send_at_command("AT+CFUN=4", |_| {})?;
Ok(())
}

/// Powers the modem off.
pub fn off() -> Result<(), Error> {
debug!("Turning modem OFF");
crate::at::send_at_command("AT+CFUN=0", |_| {})?;
Ok(())
}
Expand All @@ -117,6 +123,7 @@ pub fn off() -> Result<(), Error> {
/// Works on the nRF9160-DK (PCA10090NS) and Actinius Icarus. Other PCBs may
/// use different MAGPIO pins to control the GNSS switch.
pub fn configure_gnss_on_pca10090ns() -> Result<(), Error> {
debug!("Configuring XMAGPIO pins for 1574-1577 MHz");
// Configure the GNSS antenna. See `nrf/samples/nrf9160/gps/src/main.c`.
crate::at::send_at_command("AT%XMAGPIO=1,0,0,1,1,1574,1577", |_| {})?;
Ok(())
Expand All @@ -131,6 +138,7 @@ pub fn set_system_mode(mode: SystemMode) -> Result<(), Error> {
SystemMode::LteMAndGnss => "AT%XSYSTEMMODE=1,0,1,0",
SystemMode::NbIotAndGnss => "AT%XSYSTEMMODE=0,1,1,0",
};
debug!("{:?} => {:?}", mode, at_command);
crate::at::send_at_command(at_command, |_| {})?;
Ok(())
}
Expand All @@ -151,6 +159,7 @@ pub fn get_system_mode() -> Result<SystemMode, Error> {
} else if res.starts_with("%XSYSTEMMODE: 0,1,1,") {
result = Ok(SystemMode::NbIotAndGnss);
}
debug!("{:?} => {:?}", res, result);
})?;
result
}
Expand Down
22 changes: 19 additions & 3 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,11 @@ pub struct Socket {
pub(crate) enum SocketOption<'a> {
/// Set the host name for the TLS certificate to match
TlsHostName(&'a str),
/// 0 implies no peer verification. 1 implies peer verification is optional. 2 implies peer verification is strict (mandatory).
/// 0 implies no peer verification. 1 implies peer verification is
/// optional. 2 implies peer verification is strict (mandatory).
TlsPeerVerify(sys::nrf_sec_peer_verify_t),
/// 0 implies no TLS credential caching. 1 implies caching.
TlsSessionCache(sys::nrf_sec_session_cache_t),
/// A list of the TLS security/key tags you want to use
TlsTagList(&'a [sys::nrf_sec_tag_t]),
/// Defines the interval between each fix in seconds. The default is 1. A
Expand Down Expand Up @@ -81,10 +84,16 @@ pub(crate) enum SocketType {
pub(crate) enum SocketProtocol {
/// Used with `SocketDomain::Lte`
At,
/// Plain TCP socket
/// Plain TCP stream socket
Tcp,
/// A TLS v1.2 stream
/// Plain UDP datagram socket
Udp,
/// A TLS v1.2 over TCP stream socket
Tls1v2,
/// A TLS v1.3 over TCP stream socket
Tls1v3,
/// A DTLS v1.2 over UDP datagram socket
Dtls1v2,
/// A connection to the GPS/GNSS sub-system
Gnss,
}
Expand Down Expand Up @@ -240,6 +249,7 @@ impl<'a> SocketOption<'a> {
match self {
SocketOption::TlsHostName(_) => sys::NRF_SOL_SECURE as i32,
SocketOption::TlsPeerVerify(_) => sys::NRF_SOL_SECURE as i32,
SocketOption::TlsSessionCache(_) => sys::NRF_SOL_SECURE as i32,
SocketOption::TlsTagList(_) => sys::NRF_SOL_SECURE as i32,
SocketOption::GnssFixInterval(_) => sys::NRF_SOL_GNSS as i32,
SocketOption::GnssFixRetry(_) => sys::NRF_SOL_GNSS as i32,
Expand All @@ -253,6 +263,7 @@ impl<'a> SocketOption<'a> {
match self {
SocketOption::TlsHostName(_) => sys::NRF_SO_HOSTNAME as i32,
SocketOption::TlsPeerVerify(_) => sys::NRF_SO_SEC_PEER_VERIFY as i32,
SocketOption::TlsSessionCache(_) => sys::NRF_SO_SEC_SESSION_CACHE as i32,
SocketOption::TlsTagList(_) => sys::NRF_SO_SEC_TAG_LIST as i32,
SocketOption::GnssFixInterval(_) => sys::NRF_SO_GNSS_FIX_INTERVAL as i32,
SocketOption::GnssFixRetry(_) => sys::NRF_SO_GNSS_FIX_RETRY as i32,
Expand All @@ -266,6 +277,7 @@ impl<'a> SocketOption<'a> {
match self {
SocketOption::TlsHostName(s) => s.as_ptr() as *const sys::ctypes::c_void,
SocketOption::TlsPeerVerify(x) => x as *const _ as *const sys::ctypes::c_void,
SocketOption::TlsSessionCache(x) => x as *const _ as *const sys::ctypes::c_void,
SocketOption::TlsTagList(x) => x.as_ptr() as *const sys::ctypes::c_void,
SocketOption::GnssFixInterval(x) => x as *const _ as *const sys::ctypes::c_void,
SocketOption::GnssFixRetry(x) => x as *const _ as *const sys::ctypes::c_void,
Expand All @@ -279,6 +291,7 @@ impl<'a> SocketOption<'a> {
match self {
SocketOption::TlsHostName(s) => s.len() as u32,
SocketOption::TlsPeerVerify(x) => core::mem::size_of_val(x) as u32,
SocketOption::TlsSessionCache(x) => core::mem::size_of_val(x) as u32,
SocketOption::TlsTagList(x) => core::mem::size_of_val(x) as u32,
SocketOption::GnssFixInterval(x) => core::mem::size_of_val(x) as u32,
SocketOption::GnssFixRetry(x) => core::mem::size_of_val(x) as u32,
Expand Down Expand Up @@ -317,7 +330,10 @@ impl Into<i32> for SocketProtocol {
match self {
At => sys::NRF_PROTO_AT as i32,
Tcp => sys::NRF_IPPROTO_TCP as i32,
Udp => sys::NRF_IPPROTO_UDP as i32,
Tls1v2 => sys::NRF_SPROTO_TLS1v2 as i32,
Tls1v3 => sys::NRF_SPROTO_TLS1v3 as i32,
Dtls1v2 => sys::NRF_SPROTO_DTLS1v2 as i32,
Gnss => sys::NRF_PROTO_GNSS as i32,
}
}
Expand Down
26 changes: 16 additions & 10 deletions src/tcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

use super::{get_last_error, Error};
use crate::raw::*;
use log::debug;
use nrfxlib_sys as sys;

//******************************************************************************
Expand Down Expand Up @@ -73,13 +74,15 @@ impl TcpSocket {
/// it.
pub fn connect(&self, hostname: &str, port: u16) -> Result<(), Error> {
use core::fmt::Write;
let mut result;

debug!("Connecting via TCP to {}:{}", hostname, port);

// Now, make a null-terminated hostname
let mut hostname_smallstring: heapless::String<heapless::consts::U64> =
heapless::String::new();
write!(hostname_smallstring, "{}\0", hostname).map_err(|_| Error::HostnameTooLong)?;
// Now call getaddrinfo with some hints
let hints = crate::NrfAddrInfo {
let hints = sys::nrf_addrinfo {
ai_flags: 0,
ai_family: sys::NRF_AF_INET as i32,
ai_socktype: sys::NRF_SOCK_STREAM as i32,
Expand All @@ -89,8 +92,8 @@ impl TcpSocket {
ai_canonname: core::ptr::null_mut(),
ai_next: core::ptr::null_mut(),
};
let mut output_ptr: *mut crate::NrfAddrInfo = core::ptr::null_mut();
result = unsafe {
let mut output_ptr: *mut sys::nrf_addrinfo = core::ptr::null_mut();
let mut result = unsafe {
sys::nrf_getaddrinfo(
// hostname
hostname_smallstring.as_ptr(),
Expand All @@ -103,22 +106,25 @@ impl TcpSocket {
)
};
if (result == 0) && (!output_ptr.is_null()) {
let mut record: &crate::NrfAddrInfo = unsafe { &*output_ptr };
let mut record: &sys::nrf_addrinfo = unsafe { &*output_ptr };
loop {
let dns_addr: &crate::NrfSockAddrIn =
unsafe { &*(record.ai_addr as *const crate::NrfSockAddrIn) };
let dns_addr: &sys::nrf_sockaddr_in =
unsafe { &*(record.ai_addr as *const sys::nrf_sockaddr_in) };
// Create a new sockaddr_in with the right port
let connect_addr = crate::NrfSockAddrIn {
sin_len: core::mem::size_of::<crate::NrfSockAddrIn>() as u8,
let connect_addr = sys::nrf_sockaddr_in {
sin_len: core::mem::size_of::<sys::nrf_sockaddr_in>() as u8,
sin_family: sys::NRF_AF_INET as i32,
sin_port: htons(port),
sin_addr: dns_addr.sin_addr.clone(),
};

debug!("Trying IP address {}", &crate::NrfSockAddrIn(connect_addr));

// try and connect to this result
result = unsafe {
sys::nrf_connect(
self.socket.fd,
&connect_addr as *const crate::NrfSockAddrIn as *const _,
&connect_addr as *const sys::nrf_sockaddr_in as *const _,
connect_addr.sin_len as u32,
)
};
Expand Down
97 changes: 70 additions & 27 deletions src/tls.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! # Raw Sockets for nrfxlib
//!
//! Generic socket related code.
//! Transport Layer Security (TLS, aka SSL) socket related code.
//!
//! Copyright (c) 42 Technology Ltd 2019
//!
Expand All @@ -11,15 +11,16 @@
// Sub-Modules
//******************************************************************************

use super::{get_last_error, Error};
use crate::raw::*;
use nrfxlib_sys as sys;
// None

//******************************************************************************
// Imports
//******************************************************************************

// None
use super::{get_last_error, Error};
use crate::raw::*;
use log::debug;
use nrfxlib_sys as sys;

//******************************************************************************
// Types
Expand All @@ -29,7 +30,27 @@ use nrfxlib_sys as sys;
#[derive(Debug)]
pub struct TlsSocket {
socket: Socket,
peer_verify: i32,
}

/// Specify which version of the TLS standard to use
#[derive(Debug, Copy, Clone)]
pub enum Version {
/// TLS v1.2
Tls1v2,
/// TLS v1.3
Tls1v3,
}

/// Specify whether to verify the peer
#[derive(Debug, Copy, Clone)]
pub enum PeerVerification {
/// Yes - check the peer's certificate is valid and abort if it isn't
Enabled,
/// Maybe - check the peer's certificate is valid but don't abort if it isn't
Optional,
/// No - do not validate the peer's certificate. Using this option leaves
/// you vulnerable to man-in-the-middle attacks.
Disabled,
}

//******************************************************************************
Expand All @@ -55,38 +76,46 @@ pub struct TlsSocket {
//******************************************************************************

impl TlsSocket {
/// Create a new TLS socket. Only supports TLS v1.2 and IPv4 at the moment.
pub fn new(peer_verify: bool, security_tags: &[u32]) -> Result<TlsSocket, Error> {
let socket = Socket::new(
SocketDomain::Inet,
SocketType::Stream,
SocketProtocol::Tls1v2,
)?;
/// Create a new TLS socket. Only supports TLS v1.2/1.3 and IPv4 at the moment.
pub fn new(
peer_verify: PeerVerification,
security_tags: &[u32],
version: Version,
) -> Result<TlsSocket, Error> {
let nrf_tls_version = match version {
Version::Tls1v2 => SocketProtocol::Tls1v2,
Version::Tls1v3 => SocketProtocol::Tls1v3,
};

let socket = Socket::new(SocketDomain::Inet, SocketType::Stream, nrf_tls_version)?;

// Now configure this socket

// Set whether we verify the peer
socket.set_option(SocketOption::TlsPeerVerify(if peer_verify { 1 } else { 0 }))?;
socket.set_option(SocketOption::TlsPeerVerify(peer_verify.as_integer()))?;

// We skip the cipher list
// Always enable session caching to speed up connecting. 0 = enabled, 1
// = disabled (the default).
socket.set_option(SocketOption::TlsSessionCache(0))?;

// We don't set the cipher list, and assume the defaults are sensible.

if !security_tags.is_empty() {
// Configure the socket to use the pre-stored certificates. See
// `provision_certificates`.
socket.set_option(SocketOption::TlsTagList(security_tags))?;
}

Ok(TlsSocket {
socket,
peer_verify: if peer_verify { 1 } else { 0 },
})
Ok(TlsSocket { socket })
}

/// Look up the hostname and for each result returned, try to connect to
/// it.
pub fn connect(&self, hostname: &str, port: u16) -> Result<(), Error> {
use core::fmt::Write;

debug!("Connecting via TLS to {}:{}", hostname, port);

// First we set the hostname
self.socket
.set_option(SocketOption::TlsHostName(hostname))?;
Expand All @@ -97,7 +126,7 @@ impl TlsSocket {
heapless::String::new();
write!(hostname_smallstring, "{}\0", hostname).map_err(|_| Error::HostnameTooLong)?;
// Now call getaddrinfo with some hints
let hints = crate::NrfAddrInfo {
let hints = sys::nrf_addrinfo {
ai_flags: 0,
ai_family: sys::NRF_AF_INET as i32,
ai_socktype: sys::NRF_SOCK_STREAM as i32,
Expand All @@ -107,7 +136,7 @@ impl TlsSocket {
ai_canonname: core::ptr::null_mut(),
ai_next: core::ptr::null_mut(),
};
let mut output_ptr: *mut crate::NrfAddrInfo = core::ptr::null_mut();
let mut output_ptr: *mut sys::nrf_addrinfo = core::ptr::null_mut();
result = unsafe {
sys::nrf_getaddrinfo(
// hostname
Expand All @@ -124,23 +153,25 @@ impl TlsSocket {
if (result != 0) && output_ptr.is_null() {
return Err(Error::Nordic("tls_dns", result, get_last_error()));
} else {
let mut record: &crate::NrfAddrInfo = unsafe { &*output_ptr };
let mut record: &sys::nrf_addrinfo = unsafe { &*output_ptr };
loop {
let dns_addr: &crate::NrfSockAddrIn =
unsafe { &*(record.ai_addr as *const crate::NrfSockAddrIn) };
let dns_addr: &sys::nrf_sockaddr_in =
unsafe { &*(record.ai_addr as *const sys::nrf_sockaddr_in) };
// Create a new sockaddr_in with the right port
let connect_addr = crate::NrfSockAddrIn {
sin_len: core::mem::size_of::<crate::NrfSockAddrIn>() as u8,
let connect_addr = sys::nrf_sockaddr_in {
sin_len: core::mem::size_of::<sys::nrf_sockaddr_in>() as u8,
sin_family: sys::NRF_AF_INET as i32,
sin_port: htons(port),
sin_addr: dns_addr.sin_addr.clone(),
};

debug!("Trying IP address {}", &crate::NrfSockAddrIn(connect_addr));

// try and connect to this result
result = unsafe {
sys::nrf_connect(
self.socket.fd,
&connect_addr as *const crate::NrfSockAddrIn as *const _,
&connect_addr as *const sys::nrf_sockaddr_in as *const _,
connect_addr.sin_len as u32,
)
};
Expand Down Expand Up @@ -185,6 +216,18 @@ impl core::ops::Deref for TlsSocket {
}
}

impl PeerVerification {
/// The NRF library wants peer verification as a integer, so this function
/// converts as per `sys::nrf_sec_peer_verify_t`.
fn as_integer(self) -> u32 {
match self {
PeerVerification::Enabled => 2,
PeerVerification::Optional => 1,
PeerVerification::Disabled => 0,
}
}
}

/// Store SSL certificates in the modem NVRAM for use with a subsequent TLS
/// connection.
///
Expand Down
178 changes: 178 additions & 0 deletions src/udp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
//! # UDP Sockets for nrfxlib
//!
//! UDP socket related code.
//!
//! Copyright (c) 42 Technology Ltd 2019
//!
//! Dual-licensed under MIT and Apache 2.0. See the [README](../README.md) for
//! more details.

//******************************************************************************
// Sub-Modules
//******************************************************************************

// None

//******************************************************************************
// Imports
//******************************************************************************

use super::{get_last_error, Error};
use crate::raw::*;
use log::debug;
use nrfxlib_sys as sys;

//******************************************************************************
// Types
//******************************************************************************

/// Represents a connection to a remote TCP/IP device using plain UDP
#[derive(Debug)]
pub struct UdpSocket {
socket: Socket,
}

//******************************************************************************
// Constants
//******************************************************************************

// None

//******************************************************************************
// Global Variables
//******************************************************************************

// None

//******************************************************************************
// Macros
//******************************************************************************

// None

//******************************************************************************
// Public Functions and Impl on Public Types
//******************************************************************************

// None

//******************************************************************************
// Private Functions and Impl on Private Types
//******************************************************************************

impl UdpSocket {
/// Create a new TCP socket.
pub fn new() -> Result<UdpSocket, Error> {
let socket = Socket::new(
SocketDomain::Inet,
SocketType::Datagram,
SocketProtocol::Udp,
)?;

// Now configure this socket

Ok(UdpSocket { socket })
}

/// Look up the hostname and for each result returned, try to connect to
/// it.
pub fn connect(&self, hostname: &str, port: u16) -> Result<(), Error> {
use core::fmt::Write;

debug!("Connecting via UDP to {}:{}", hostname, port);

// Now, make a null-terminated hostname
let mut hostname_smallstring: heapless::String<heapless::consts::U64> =
heapless::String::new();
write!(hostname_smallstring, "{}\0", hostname).map_err(|_| Error::HostnameTooLong)?;
// Now call getaddrinfo with some hints
let hints = sys::nrf_addrinfo {
ai_flags: 0,
ai_family: sys::NRF_AF_INET as i32,
ai_socktype: sys::NRF_SOCK_DGRAM as i32,
ai_protocol: 0,
ai_addrlen: 0,
ai_addr: core::ptr::null_mut(),
ai_canonname: core::ptr::null_mut(),
ai_next: core::ptr::null_mut(),
};
let mut output_ptr: *mut sys::nrf_addrinfo = core::ptr::null_mut();
let mut result = unsafe {
sys::nrf_getaddrinfo(
// hostname
hostname_smallstring.as_ptr(),
// service
core::ptr::null(),
// hints
&hints,
// output pointer
&mut output_ptr,
)
};
if (result == 0) && (!output_ptr.is_null()) {
let mut record: &sys::nrf_addrinfo = unsafe { &*output_ptr };
loop {
let dns_addr: &sys::nrf_sockaddr_in =
unsafe { &*(record.ai_addr as *const sys::nrf_sockaddr_in) };
// Create a new sockaddr_in with the right port
let connect_addr = sys::nrf_sockaddr_in {
sin_len: core::mem::size_of::<sys::nrf_sockaddr_in>() as u8,
sin_family: sys::NRF_AF_INET as i32,
sin_port: htons(port),
sin_addr: dns_addr.sin_addr.clone(),
};

debug!("Trying IP address {}", &crate::NrfSockAddrIn(connect_addr));

// try and connect to this result
result = unsafe {
sys::nrf_connect(
self.socket.fd,
&connect_addr as *const sys::nrf_sockaddr_in as *const _,
connect_addr.sin_len as u32,
)
};
if result == 0 {
break;
}
if !record.ai_next.is_null() {
record = unsafe { &*record.ai_next };
} else {
break;
}
}
unsafe {
sys::nrf_freeaddrinfo(output_ptr);
}
}
if result != 0 {
Err(Error::Nordic("udp_connect", result, get_last_error()))
} else {
Ok(())
}
}
}

impl Pollable for UdpSocket {
/// Get the underlying socket ID for this socket.
fn get_fd(&self) -> i32 {
self.socket.fd
}
}

impl core::ops::DerefMut for UdpSocket {
fn deref_mut(&mut self) -> &mut Socket {
&mut self.socket
}
}

impl core::ops::Deref for UdpSocket {
type Target = Socket;
fn deref(&self) -> &Socket {
&self.socket
}
}

//******************************************************************************
// End of File
//******************************************************************************