blob: 05a1ccac7f9be990dc19935bbcaa1844d7906400 [file]
// Copyright 2026 The Pigweed Authors
//
// Licensed 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
//
// https://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.
use core::mem;
use pw_status::{Error, Result};
use userspace::syscall::{self, Signals};
use userspace::time::Instant;
use zerocopy::IntoBytes;
use crate::defs::{RequestHeader, RequestType, ResponseHeader};
/// A `stream_protocol` client.
pub struct StreamClient {
ipc_handle: u32,
}
impl StreamClient {
/// Creates a new `StreamClient` instance for the given channel handle.
#[must_use]
pub const fn new(ipc_handle: u32) -> Self {
Self { ipc_handle }
}
/// Prepares an `ipc_buffer` for a write transaction
///
/// TODO: https://pwbug.dev/509941221 - Use vectorized IO to avoid copying
/// headers and data into a temporary buffer.
fn prepare_write_transaction(write_data: &[u8], ipc_buffer: &mut [u8]) -> Result<usize> {
let header = RequestHeader {
request_type: RequestType::Write,
};
let header_bytes = header.as_bytes();
if header_bytes.len() + write_data.len() > ipc_buffer.len() {
return Err(Error::OutOfRange);
}
ipc_buffer[..header_bytes.len()].copy_from_slice(header_bytes);
let start = header_bytes.len();
let end = start + write_data.len();
ipc_buffer[start..end].copy_from_slice(write_data);
Ok(header_bytes.len() + write_data.len())
}
/// Verifies and extracts the read payload from the raw response buffer.
///
/// TODO: https://pwbug.dev/509941221 - Use vectorized IO to avoid copying
/// headers and data into a temporary buffer.
fn extract_read_payload(
response_header: &ResponseHeader,
response_buffer: &[u8],
response_length: usize,
destination_buffer: &mut [u8],
) -> Result<usize> {
let payload_length = response_length - mem::size_of::<ResponseHeader>();
if payload_length != response_header.length as usize {
return Err(Error::InvalidArgument);
}
if payload_length > destination_buffer.len() {
return Err(Error::OutOfRange);
}
let start = mem::size_of::<ResponseHeader>();
let end = start + payload_length;
destination_buffer[..payload_length].copy_from_slice(&response_buffer[start..end]);
Ok(payload_length)
}
/// Writes a byte slice to the IPC stream channel with a given deadline.
pub fn write(&self, data: &[u8], deadline: Instant) -> Result<usize> {
let mut request_buffer = [0u8; 128];
let request_length = Self::prepare_write_transaction(data, &mut request_buffer)?;
let mut response_buffer = [0u8; mem::size_of::<ResponseHeader>()];
let response_length = syscall::channel_transact(
self.ipc_handle,
&request_buffer[..request_length],
&mut response_buffer,
deadline,
)?;
let response_header = ResponseHeader::try_from_bytes(&response_buffer[..response_length])?;
response_header.result()?;
Ok(response_header.length as usize)
}
/// Writes a string slice to the IPC stream channel with a given deadline.
pub fn write_str(&self, text: &str, deadline: Instant) -> Result<usize> {
self.write(text.as_bytes(), deadline)
}
/// Reads data from the IPC stream channel into the destination buffer with a given deadline.
pub fn read(&self, destination_buffer: &mut [u8], deadline: Instant) -> Result<usize> {
syscall::object_wait(self.ipc_handle, Signals::USER, deadline)?;
let header = RequestHeader {
request_type: RequestType::Read,
};
let mut response_buffer = [0u8; 128];
let response_length = syscall::channel_transact(
self.ipc_handle,
header.as_bytes(),
&mut response_buffer,
deadline,
)?;
let response_header = ResponseHeader::try_from_bytes(&response_buffer[..response_length])?;
response_header.result()?;
Self::extract_read_payload(
&response_header,
&response_buffer,
response_length,
destination_buffer,
)
}
}