Skip to content

Commit

Permalink
feat: use typestate pattern for UdpSocket and TcpSocket (#17)
Browse files Browse the repository at this point in the history
* wip

* wip

* feat: implement typestate pattern for UdpSocket and TcpSocket

* feat: implement socket file descriptor abstraction

That removes the need for a custom drop for upd and tcp sockets

* chore: add .clippy.toml configuration

* chore: clippy configuration

* style: implement clippy suggestions

* refactor: remove need for custom drop impl
  • Loading branch information
lorenzofelletti authored Jul 17, 2024
1 parent b50abab commit a13df10
Show file tree
Hide file tree
Showing 15 changed files with 393 additions and 340 deletions.
3 changes: 3 additions & 0 deletions .clippy.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
doc-valid-idents = ["PlayStation", ".."]

allowed-prefixes = ["To", ".."]
32 changes: 19 additions & 13 deletions src/dns.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
#![allow(clippy::module_name_repetitions)]

use alloc::{
borrow::ToOwned,
string::{String, ToString},
Expand All @@ -8,7 +10,7 @@ use embedded_io::{Read, Write};
use embedded_nal::{IpAddr, Ipv4Addr, SocketAddr};
use psp::sys::in_addr;

use crate::socket::udp::UdpSocketState;
use crate::socket::state::Connected;

use super::{
socket::{udp::UdpSocket, ToSocketAddr},
Expand All @@ -31,7 +33,7 @@ pub fn create_a_type_query(domain: &str) -> Question {
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DnsError {
/// The DNS resolver failed to create
FailedToCreate,
FailedToCreate(String),
/// The hostname could not be resolved
HostnameResolutionFailed(String),
/// The IP address could not be resolved
Expand All @@ -41,7 +43,7 @@ pub enum DnsError {
/// A DNS resolver
pub struct DnsResolver {
/// The UDP socket that is used to send and receive DNS messages
udp_socket: UdpSocket,
udp_socket: UdpSocket<Connected>,
/// The DNS server address
dns: SocketAddr,
}
Expand All @@ -57,10 +59,14 @@ impl DnsResolver {
/// happen if the socket could not be created or bound to the specified address
#[allow(unused)]
pub fn new(dns: SocketAddr) -> Result<Self, DnsError> {
let mut udp_socket = UdpSocket::new().map_err(|_| DnsError::FailedToCreate)?;
udp_socket
let udp_socket = UdpSocket::new()
.map_err(|_| DnsError::FailedToCreate("Failed to create socket".to_owned()))?;
let udp_socket = udp_socket
.bind(None) // binds to None, otherwise the socket errors for some reason
.map_err(|_| DnsError::FailedToCreate)?;
.map_err(|_| DnsError::FailedToCreate("Failed to bind socket".to_owned()))?;
let udp_socket = udp_socket
.connect(dns)
.map_err(|_| DnsError::FailedToCreate("Failed to connect socket".to_owned()))?;

Ok(DnsResolver { udp_socket, dns })
}
Expand Down Expand Up @@ -89,13 +95,6 @@ impl DnsResolver {
/// This may happen if the connection of the socket fails, or if the DNS server
/// does not answer the query, or any other error occurs
pub fn resolve(&mut self, host: &str) -> Result<in_addr, DnsError> {
// connect to the DNS server, if not already
if self.udp_socket.get_state() != UdpSocketState::Connected {
self.udp_socket
.connect(self.dns)
.map_err(|e| DnsError::HostnameResolutionFailed(e.to_string()))?;
}

// create a new query
let mut questions = [super::dns::create_a_type_query(host)];
let query = dns_protocol::Message::new(
Expand Down Expand Up @@ -166,6 +165,13 @@ impl DnsResolver {
)),
}
}

/// Get the [`SocketAddr`] of the DNS server
#[must_use]
#[inline]
pub fn dns(&self) -> SocketAddr {
self.dns
}
}

impl traits::dns::ResolveHostname for DnsResolver {
Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#![no_std]
#![feature(trait_alias)]
#![doc = include_str!("../README.md")]
#![allow(clippy::cast_sign_loss)]
#![allow(clippy::cast_possible_truncation)]
#![allow(clippy::cast_possible_wrap)]

extern crate alloc;

Expand Down
9 changes: 0 additions & 9 deletions src/socket/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,6 @@ use core::fmt::Display;
/// An error that can occur with a socket
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum SocketError {
/// The socket is not connected
NotConnected,
/// The socket is already connected
AlreadyConnected,
/// The socket is already bound
AlreadyBound,
/// The socket is not bound
NotBound,
/// Unsupported address family
UnsupportedAddressFamily,
/// Socket error with errno
Expand All @@ -29,7 +21,6 @@ impl Display for SocketError {
impl embedded_io::Error for SocketError {
fn kind(&self) -> embedded_io::ErrorKind {
match self {
SocketError::NotConnected => embedded_io::ErrorKind::NotConnected,
SocketError::UnsupportedAddressFamily => embedded_io::ErrorKind::Unsupported,
_ => embedded_io::ErrorKind::Other,
}
Expand Down
4 changes: 4 additions & 0 deletions src/socket/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
#![allow(clippy::module_name_repetitions)]

use embedded_nal::{Ipv4Addr, SocketAddrV4};
use psp::sys::{in_addr, sockaddr};

use super::netc;

pub mod error;
pub mod sce;
pub mod state;
pub mod tcp;
pub mod tls;
pub mod udp;
Expand Down
52 changes: 52 additions & 0 deletions src/socket/sce.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use core::ops::Deref;

use alloc::rc::Rc;
use psp::sys;

/// Raw socket file descriptor
///
/// This is a wrapper around a raw socket file descriptor, which
/// takes care of closing it when no other references to it exist.
///
/// # Notes
/// The drop implementation of this type calls the close syscall.
/// Closing via drop is best-effort as of now (errors are ignored).
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) struct RawSocketFileDescriptor(pub(crate) i32);

impl Drop for RawSocketFileDescriptor {
fn drop(&mut self) {
unsafe {
sys::sceNetInetClose(self.0);
};
}
}

impl Deref for RawSocketFileDescriptor {
type Target = i32;

fn deref(&self) -> &Self::Target {
&self.0
}
}

/// Socket file descriptor
///
/// This is a wrapper around a raw socket file descriptor, which
/// takes care of closing it when no other references to it exist.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SocketFileDescriptor(pub(crate) Rc<RawSocketFileDescriptor>);

impl SocketFileDescriptor {
pub(crate) fn new(fd: i32) -> Self {
Self(Rc::new(RawSocketFileDescriptor(fd)))
}
}

impl Deref for SocketFileDescriptor {
type Target = i32;

fn deref(&self) -> &Self::Target {
&self.0
}
}
27 changes: 27 additions & 0 deletions src/socket/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use core::fmt::Debug;

/// Trait describing the state of a socket
pub trait SocketState: Debug {}

/// Socket is in an unbound state
#[derive(Debug)]
pub struct Unbound;
impl SocketState for Unbound {}

/// Socket is in a bound state
#[derive(Debug)]
pub struct Bound;
impl SocketState for Bound {}

/// Socket is in a connected state
#[derive(Debug)]
pub struct Connected;
impl SocketState for Connected {}

#[derive(Debug)]
pub struct NotReady;
impl SocketState for NotReady {}

#[derive(Debug)]
pub struct Ready;
impl SocketState for Ready {}
Loading

0 comments on commit a13df10

Please sign in to comment.