iggy_common 0.10.0

Iggy is the persistent message streaming platform written in Rust, supporting QUIC, TCP and HTTP transport protocols, capable of processing millions of messages per second.
Documentation
/* Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

mod quic_sender;
mod tcp_sender;
mod tcp_tls_sender;
mod websocket_sender;
mod websocket_tls_sender;

pub use quic_sender::QuicSender;
pub use tcp_sender::TcpSender;
pub use tcp_tls_sender::TcpTlsSender;
pub use websocket_sender::WebSocketSender;
pub use websocket_tls_sender::WebSocketTlsSender;

use crate::IggyError;
use crate::alloc::buffer::PooledBuffer;
use compio::BufResult;
use compio::buf::IoBufMut;
use compio::io::{AsyncReadExt, AsyncWriteExt};
use compio::net::TcpStream;
use compio::quic::{RecvStream, SendStream};
use compio::tls::TlsStream;
use std::future::Future;
#[cfg(unix)]
use std::os::fd::{AsFd, OwnedFd};
use tracing::debug;
#[cfg(unix)]
use tracing::error;

macro_rules! forward_async_methods {
    (
        $(
            async fn $method_name:ident
            $(<$($generic:ident $(: $bound:path)?),+>)?
            (
                &mut self $(, $arg:ident : $arg_ty:ty )*
            ) -> $ret:ty ;
        )*
    ) => {
        $(
            pub async fn $method_name
            $(<$($generic $(: $bound)?),+>)?
            (&mut self, $( $arg: $arg_ty ),* ) -> $ret {
                match self {
                    Self::Tcp(d) => d.$method_name$(::<$($generic),+>)?($( $arg ),*).await,
                    Self::TcpTls(s) => s.$method_name$(::<$($generic),+>)?($( $arg ),*).await,
                    Self::Quic(s) => s.$method_name$(::<$($generic),+>)?($( $arg ),*).await,
                    Self::WebSocket(s) => s.$method_name$(::<$($generic),+>)?($( $arg ),*).await,
                    Self::WebSocketTls(s) => s.$method_name$(::<$($generic),+>)?($( $arg ),*).await,
                }
            }
        )*
    }
}

pub trait Sender {
    fn read<B: IoBufMut>(&mut self, buffer: B) -> impl Future<Output = (Result<(), IggyError>, B)>;
    fn send_empty_ok_response(&mut self) -> impl Future<Output = Result<(), IggyError>>;
    fn send_ok_response(&mut self, payload: &[u8]) -> impl Future<Output = Result<(), IggyError>>;
    fn send_ok_response_vectored(
        &mut self,
        length: &[u8],
        slices: Vec<PooledBuffer>,
    ) -> impl Future<Output = Result<(), IggyError>>;
    fn send_error_response(
        &mut self,
        error: IggyError,
    ) -> impl Future<Output = Result<(), IggyError>>;
    fn shutdown(&mut self) -> impl Future<Output = Result<(), IggyError>>;
}

#[allow(clippy::large_enum_variant)]
#[derive(Debug)]
pub enum SenderKind {
    Tcp(TcpSender),
    TcpTls(TcpTlsSender),
    Quic(QuicSender),
    WebSocket(WebSocketSender),
    WebSocketTls(WebSocketTlsSender),
}

impl SenderKind {
    pub fn get_tcp_sender(stream: TcpStream) -> Self {
        Self::Tcp(TcpSender {
            stream: Some(stream),
        })
    }

    pub fn get_tcp_tls_sender(stream: TlsStream<TcpStream>) -> Self {
        Self::TcpTls(TcpTlsSender { stream })
    }

    pub fn get_quic_sender(send_stream: SendStream, recv_stream: RecvStream) -> Self {
        Self::Quic(QuicSender {
            send: send_stream,
            recv: recv_stream,
        })
    }

    pub fn get_websocket_sender(stream: WebSocketSender) -> Self {
        Self::WebSocket(stream)
    }

    pub fn get_websocket_tls_sender(stream: WebSocketTlsSender) -> Self {
        Self::WebSocketTls(stream)
    }

    #[cfg(unix)]
    pub fn take_and_migrate_tcp(&mut self) -> Option<OwnedFd> {
        match self {
            SenderKind::Tcp(tcp_sender) => {
                let stream = tcp_sender.stream.take()?;
                let poll_fd = stream.into_poll_fd().ok()?;

                let raw_fd = poll_fd.as_fd();
                let Ok(owned_fd) = nix::unistd::dup(raw_fd) else {
                    // TODO(tungtose): recover tcp stream?
                    error!("Failed to dup fd");
                    return None;
                };

                Some(owned_fd)
            }
            // TODO(tungtose): support TCP TLS
            _ => None,
        }
    }

    forward_async_methods! {
        async fn read<B: IoBufMut>(&mut self, buffer: B) -> (Result<(), IggyError>, B);
        async fn send_empty_ok_response(&mut self) -> Result<(), IggyError>;
        async fn send_ok_response(&mut self, payload: &[u8]) -> Result<(), IggyError>;
        async fn send_ok_response_vectored(&mut self, length: &[u8], slices: Vec<PooledBuffer>) -> Result<(), IggyError>;
        async fn send_error_response(&mut self, error: IggyError) -> Result<(), IggyError>;
        async fn shutdown(&mut self) -> Result<(), IggyError>;
    }
}

const STATUS_OK: &[u8] = &[0; 4];

pub(crate) async fn read<T, B>(stream: &mut T, buffer: B) -> (Result<(), IggyError>, B)
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
    B: IoBufMut,
{
    let BufResult(result, buffer) = stream.read_exact(buffer).await;
    match (result, buffer) {
        (Ok(_), buffer) => (Ok(()), buffer),
        (Err(e), buffer) => {
            if e.kind() == std::io::ErrorKind::UnexpectedEof {
                (Err(IggyError::ConnectionClosed), buffer)
            } else {
                (Err(IggyError::TcpError), buffer)
            }
        }
    }
}

pub(crate) async fn send_empty_ok_response<T>(stream: &mut T) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    send_ok_response(stream, &[]).await
}

pub(crate) async fn send_ok_response<T>(stream: &mut T, payload: &[u8]) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    send_response(stream, STATUS_OK, payload).await
}

pub(crate) async fn send_ok_response_vectored<T>(
    stream: &mut T,
    length: &[u8],
    slices: Vec<PooledBuffer>,
) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    send_response_vectored(stream, STATUS_OK, length, slices).await
}

pub(crate) async fn send_error_response<T>(
    stream: &mut T,
    error: IggyError,
) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    send_response(stream, &error.as_code().to_le_bytes(), &[]).await
}

pub(crate) async fn send_response<T>(
    stream: &mut T,
    status: &[u8],
    payload: &[u8],
) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    debug!(
        "Sending response of len: {} with status: {:?}...",
        payload.len(),
        status
    );
    let length = (payload.len() as u32).to_le_bytes();
    stream
        .write_all([status, &length, payload].concat())
        .await
        .0
        .map_err(|_| IggyError::TcpError)?;
    debug!("Sent response with status: {:?}", status);
    Ok(())
}

pub(crate) async fn send_response_vectored<T>(
    stream: &mut T,
    status: &[u8],
    length: &[u8],
    mut slices: Vec<PooledBuffer>,
) -> Result<(), IggyError>
where
    T: AsyncReadExt + AsyncWriteExt + Unpin,
{
    let resp_status = u32::from_le_bytes(status.try_into().unwrap());
    debug!(
        "Sending vectored response of len: {} with status: {:?}...",
        slices.len(),
        resp_status
    );
    let status = PooledBuffer::from(status);
    let length = PooledBuffer::from(length);
    slices.splice(0..0, [status, length]);
    stream
        .write_vectored_all(slices)
        .await
        .0
        .map_err(|_| IggyError::TcpError)?;
    debug!("Sent response with status: {:?}", resp_status);
    Ok(())
}