use std::sync::Arc;
use crate::error::{Result, StatusCode};
use crate::file_descriptor;
use crate::parcel::Parcel;
use crate::shared_memory::{
IMemoryHeap, MappedHeap, MemoryHeapBase, FLAG_READ_ONLY, SEAL_FUTURE_WRITE, SEAL_SHRINK,
SEAL_WRITE,
};
pub const BLOB_INPLACE_LIMIT: usize = 16 * 1024;
pub const BLOB_INPLACE: i32 = 0;
pub const BLOB_ASHMEM_IMMUTABLE: i32 = 1;
pub const BLOB_ASHMEM_MUTABLE: i32 = 2;
const BLOB_REGION_NAME: &str = "Parcel Blob";
#[derive(Debug)]
pub enum Blob {
Inline(Vec<u8>),
Shared {
heap: Arc<MappedHeap>,
len: usize,
mutable: bool,
},
}
impl Blob {
pub fn len(&self) -> usize {
match self {
Blob::Inline(bytes) => bytes.len(),
Blob::Shared { len, .. } => *len,
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn is_mutable(&self) -> bool {
matches!(self, Blob::Shared { mutable: true, .. })
}
pub fn to_vec(&self) -> Result<Vec<u8>> {
match self {
Blob::Inline(bytes) => Ok(bytes.clone()),
Blob::Shared { heap, len, .. } => {
if *len > heap.size() {
return Err(StatusCode::BadValue);
}
let mut out = vec![0u8; *len];
heap.read_at(0, &mut out)?;
Ok(out)
}
}
}
pub fn inline(&self) -> Option<&[u8]> {
match self {
Blob::Inline(bytes) => Some(bytes),
Blob::Shared { .. } => None,
}
}
pub fn mapped(&self) -> Option<&Arc<MappedHeap>> {
match self {
Blob::Inline(_) => None,
Blob::Shared { heap, .. } => Some(heap),
}
}
}
impl Parcel {
pub fn write_blob(&mut self, data: &[u8], mutable_copy: bool) -> Result<()> {
let len = i32::try_from(data.len()).map_err(|_| StatusCode::BadValue)?;
let shared = data.len() > BLOB_INPLACE_LIMIT
&& self.allow_fds()
&& crate::shared_memory::is_supported();
let heap = if shared {
let flags = if mutable_copy { 0 } else { FLAG_READ_ONLY };
let heap = MemoryHeapBase::new_named(data.len(), flags, BLOB_REGION_NAME)?;
let write_sealed = heap
.seals()
.is_some_and(|s| s & (SEAL_WRITE | SEAL_FUTURE_WRITE) != 0);
(mutable_copy || write_sealed).then_some(heap)
} else {
None
};
let Some(heap) = heap else {
self.write::<i32>(&len)?;
self.write::<i32>(&BLOB_INPLACE)?;
return self.write_aligned_data(data);
};
heap.write_at(0, data)?;
let fd = heap.to_parcel_fd()?;
self.write::<i32>(&len)?;
self.write::<i32>(&if mutable_copy {
BLOB_ASHMEM_MUTABLE
} else {
BLOB_ASHMEM_IMMUTABLE
})?;
file_descriptor::write_raw_fd(self, std::os::fd::AsFd::as_fd(fd.as_ref()))
}
pub fn read_blob(&mut self) -> Result<Blob> {
let len = self.read::<i32>()?;
if len < 0 {
return Err(StatusCode::UnexpectedNull);
}
let len = len as usize;
let tag = self.read::<i32>()?;
if tag == BLOB_INPLACE {
return Ok(Blob::Inline(self.read_aligned_data(len)?.to_vec()));
}
if tag != BLOB_ASHMEM_IMMUTABLE && tag != BLOB_ASHMEM_MUTABLE {
log::error!("read_blob: unknown blob tag {tag}");
return Err(StatusCode::BadValue);
}
let mutable = tag == BLOB_ASHMEM_MUTABLE;
let fd = file_descriptor::read_raw_fd(self)?;
let shrink_sealed =
crate::shared_memory::heap::fd_seals(&fd).is_some_and(|s| s & SEAL_SHRINK != 0);
if !shrink_sealed
&& !crate::shared_memory::shared::is_ashmem_fd(std::os::fd::AsFd::as_fd(&fd))
{
log::error!("read_blob: the blob fd is neither a sealed memfd nor ashmem");
return Err(StatusCode::BadValue);
}
if len == 0 {
log::error!("read_blob: a shared blob of zero length has no region to map");
return Err(StatusCode::BadValue);
}
let flags = if mutable { 0 } else { FLAG_READ_ONLY };
let heap = MappedHeap::from_fd(fd, len, 0, flags)?;
Ok(Blob::Shared {
heap: Arc::new(heap),
len,
mutable,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pattern(len: usize) -> Vec<u8> {
(0..len).map(|i| (i % 251) as u8).collect()
}
fn shared_form_available(mutable_copy: bool) -> bool {
if !crate::shared_memory::is_supported() {
return false;
}
mutable_copy
|| MemoryHeapBase::new_named(4096, FLAG_READ_ONLY, BLOB_REGION_NAME)
.ok()
.and_then(|h| h.seals())
.is_some_and(|s| s & (SEAL_WRITE | SEAL_FUTURE_WRITE) != 0)
}
#[test]
fn the_inplace_limit_decides_the_form() {
for (len, want_inline) in [
(0usize, true),
(1, true),
(BLOB_INPLACE_LIMIT - 1, true),
(BLOB_INPLACE_LIMIT, true),
(BLOB_INPLACE_LIMIT + 1, false),
] {
let data = pattern(len);
let mut parcel = Parcel::new();
assert!(parcel.allow_fds());
parcel.write_blob(&data, false).expect("write_blob");
parcel.set_data_position(0);
let blob = parcel.read_blob().expect("read_blob");
assert_eq!(blob.len(), len, "len {len}");
let want_inline = want_inline || !shared_form_available(false);
assert_eq!(
blob.inline().is_some(),
want_inline,
"len {len} took the wrong form"
);
assert_eq!(blob.to_vec().expect("to_vec"), data, "len {len} round trip");
assert!(!blob.is_mutable());
}
}
#[test]
fn a_mutable_blob_is_writable_and_an_immutable_one_is_not() {
let data = pattern(BLOB_INPLACE_LIMIT + 4096);
if !shared_form_available(true) {
return;
}
let mut parcel = Parcel::new();
parcel.write_blob(&data, true).expect("write_blob mutable");
parcel.set_data_position(0);
let blob = parcel.read_blob().expect("read_blob");
assert!(blob.is_mutable());
let heap = blob.mapped().expect("a large blob is shared").clone();
heap.write_at(0, b"edit")
.expect("a mutable region takes a write");
assert_eq!(&blob.to_vec().expect("to_vec")[..4], b"edit");
if !shared_form_available(false) {
return;
}
let mut parcel = Parcel::new();
parcel
.write_blob(&data, false)
.expect("write_blob immutable");
parcel.set_data_position(0);
let blob = parcel.read_blob().expect("read_blob");
assert!(!blob.is_mutable());
let heap = blob.mapped().expect("a large blob is shared");
assert!(
heap.write_at(0, b"edit").is_err(),
"an immutable blob must not accept a write"
);
assert_ne!(
heap.seals().expect("seals") & (SEAL_WRITE | SEAL_FUTURE_WRITE),
0,
"an immutable blob's fd must be write-sealed"
);
}
#[test]
fn a_null_blob_is_distinguishable_from_an_empty_one() {
let mut parcel = Parcel::new();
parcel.write::<i32>(&-1).expect("write null marker");
parcel.set_data_position(0);
assert_eq!(parcel.read_blob().err(), Some(StatusCode::UnexpectedNull));
let mut parcel = Parcel::new();
parcel.write_blob(&[], false).expect("write_blob empty");
parcel.set_data_position(0);
let blob = parcel.read_blob().expect("read_blob");
assert!(blob.is_empty());
assert_eq!(blob.to_vec().expect("to_vec"), Vec::<u8>::new());
}
#[test]
fn a_parcel_that_cannot_carry_an_fd_writes_the_payload_inline() {
let data = pattern(1024 * 1024);
let mut parcel = Parcel::new_data_only();
assert!(!parcel.allow_fds());
parcel.write_blob(&data, true).expect("write_blob");
let bytes = parcel.into_bytes().expect("into_bytes");
assert!(bytes.len() >= data.len() + 8);
let mut parcel = Parcel::from_slice(&bytes);
let blob = parcel.read_blob().expect("read_blob");
assert_eq!(blob.inline().map(<[u8]>::len), Some(data.len()));
assert_eq!(blob.to_vec().expect("to_vec"), data);
assert!(!blob.is_mutable());
}
#[test]
fn an_unknown_tag_is_refused() {
let mut parcel = Parcel::new();
parcel.write::<i32>(&8).expect("len");
parcel.write::<i32>(&7).expect("bogus tag");
parcel.set_data_position(0);
assert_eq!(parcel.read_blob().err(), Some(StatusCode::BadValue));
}
}