Skip to content

feature request: Reader/Writer support, ability to split to Reader and Writer (maybe also Full-Duplex) #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
yanshay opened this issue Jan 24, 2025 · 5 comments

Comments

@yanshay
Copy link
Contributor

yanshay commented Jan 24, 2025

I'm using picoserve as http server and it seem to require the ability to work with Reader and Writer (sammhicks/picoserve#71).

I saw in #59 reference to full-duplex which probably covers this. I don't think picoserve needs full-duplex, just need separate writer/reader.

Submitting the issue as a reminder. Will look into this myself though probably beyond my understanding of this crate.

@yanshay
Copy link
Contributor Author

yanshay commented Jan 27, 2025

Not a proper solution and not full duplex, but the below allow using picoserve with esp-mbedtls as is, in case someone needs to.

Not all capabilities there will work, just the simple stuff.

pub struct SessionWrapper<'a> {
    session: Rc<Mutex<NoopRawMutex, Session<'a, TcpSocket<'a>>>>,
}

impl<'a,'s> SessionWrapper<'s>
where
    's: 'a
{
    pub fn new(session: Session<'s, TcpSocket<'s>>) -> Self {
        Self {
            session: Rc::new(Mutex::new(session))
        }
    }
}

// Reader

pub struct SessionReader<'a> {
    session: Rc<Mutex<NoopRawMutex, Session<'a, TcpSocket<'a>>>>
}

impl<'a> embedded_io_async::ErrorType for SessionReader<'a> {
    type Error = TlsError;
}

impl<'a> embedded_io_async::Read for SessionReader<'a> {
    async fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
        let mut session = self.session.lock().await;
        let res = session.read(buf).await;
        res
    }
}

// Writer

pub struct SessionWriter<'a>
{
        session: Rc<Mutex<NoopRawMutex, Session<'a, TcpSocket<'a>>>>,
}

impl<'a> embedded_io_async::ErrorType for SessionWriter<'a>
{
    type Error = TlsError;
}

impl<'a> embedded_io_async::Write for SessionWriter<'a>
{
    async fn write(&mut self, buf: &[u8]) -> Result<usize, Self::Error> {
        let mut session = self.session.lock().await;
        let res = session.write(buf).await;
        res
    }

    async fn flush(&mut self) -> Result<(), Self::Error> {
        let mut session = self.session.lock().await;
        let res = session.flush().await;
        res
    }
}

// Implement picoserve Socket on SessionWrapper

impl <'s> picoserve::io::Socket for SessionWrapper<'s>{
    type Error = TlsError;
    type ReadHalf<'a> = SessionReader<'s> where 's: 'a;
    type WriteHalf<'a> = SessionWriter<'s> where 's : 'a;

    fn split(&mut self) -> (Self::ReadHalf<'_>, Self::WriteHalf<'_>) {
        (
            SessionReader { session: self.session.clone() },
            SessionWriter { session: self.session.clone() },
        )
    }

    async fn shutdown<Timer: picoserve::Timer>(
        mut self,
        _timeouts: &picoserve::Timeouts<Timer::Duration>,
        _timer: &mut Timer,
    ) -> Result<(), picoserve::Error<Self::Error>> {
        Ok(())
    }
}

// Copied from picoserve since need to disable embassy feature of picoserve

struct EmbassyTimer;

impl picoserve::time::Timer for EmbassyTimer {
    type Duration = embassy_time::Duration;
    type TimeoutError = embassy_time::TimeoutError;

    async fn run_with_timeout<F: core::future::Future>(
        &mut self,
        duration: Self::Duration,
        future: F,
    ) -> Result<F::Output, Self::TimeoutError> {
        embassy_time::with_timeout(duration, future).await
    }
}

@muhammetaliakbay
Copy link

muhammetaliakbay commented Mar 2, 2025

@yanshay I am working on such a feature, if you would like to check it:
https://github.com/muhammetaliakbay/esp-mbedtls on main branch.

The usage:

  • call share() method on the async Session
  • the result of the method is a SharedSession instance which can be accessed via immutable references
  • the async Read and Write types are implemented for that immutable references
let tls = TlsSession::new(
    &mut socket,
    esp_mbedtls::Mode::Client {
        servername: HOSTNAME,
    },
    esp_mbedtls::TlsVersion::Tls1_3,
    certificates,
    self.tls.clone(),
)?.share();

tls.connect()
    .await.map_err(|e| anyhow::anyhow!("Failed to establish TLS connection: {:?}", e))?;

let mut tls = &tls;

// now tls implements Read, Write and it is copyable because it is just an immutable reference.

The solution is still very premature, I will prepare some proper tests and documentation for it.

@AnthonyGrondin
Copy link
Collaborator

I think, if possible, we should take inspiration from embassy-net and how it handles split, to make an API that is somewhat idiomatic to embedded networking. Session could be turned into TlsIo and implement both a Reader, a Writer and a Socket.

See: https://github.com/embassy-rs/embassy/blob/765094887d99bcdce624ac483f076a02079a5c74/embassy-net/src/tcp.rs#L470-L474

@muhammetaliakbay
Copy link

I believe my approach is very similar to it already. The "SharedSession" can be both reader and writer at the same time as it is just a mutex wrapper around the actual Session instance. The actual point is to use the ReadReady and WriteReady traits to be able to avoid flushes that can cause deadlocks.

@yanshay
Copy link
Contributor Author

yanshay commented Mar 4, 2025

I think what @AnthonyGrondin points to and I tend to agree is that to use this with the embedded ecosystem, implementing the embassy-net traits is mandatory for this to be usable.
For example, it's required for picoserve because it expects those traits. Wont work without.
It may be possible to implement that on top of the SharedSession by the user or it can come as part of esp-mbedtls (probable better).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants