use std::io::{Read, Write};
use super::address::RpcAddress;
use super::transport::MAX_FRAME_LEN;
use super::wire::{WireCodec, WireMessage, WireReply, WireTransaction};
use super::{RpcError, RpcResult};
const WIRE_HEADER_LEN: usize = 16;
pub const A13_ADDR_LEN: usize = 8;
pub const A13_TXN_FIXED_LEN: usize = A13_ADDR_LEN + 4 + 4 + 8 + 16; pub const A13_DEC_STRONG_LEN: usize = A13_ADDR_LEN + 4 + 4; pub const A13_CONN_HEADER_LEN: usize = 16;
pub const A13_NEW_SESSION_RESP_LEN: usize = 8;
pub const A13_CONN_INIT_LEN: usize = 8;
const CMD_TRANSACT: u32 = 0;
const CMD_REPLY: u32 = 1;
const CMD_DEC_STRONG: u32 = 2;
const ADDR_OPTION_CREATED: u32 = 1 << 0;
const ADDR_OPTION_FOR_SERVER: u32 = 1 << 1;
pub const CONN_OPTION_INCOMING: u8 = 0x1;
pub const CONN_INIT_OKAY: [u8; 4] = *b"cci\0";
pub const PROTOCOL_V0: u32 = 0;
pub const PROTOCOL_V1: u32 = 1;
pub const PROTOCOL_V2: u32 = 2;
pub const SUPPORTED_MAX_VERSION: u32 = PROTOCOL_V2;
pub const RPC_WIRE_PROTOCOL_VERSION_NEXT: u32 = 3;
pub const RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL: u32 = 0xF000_0000;
pub const FD_MODE_NONE: u8 = 0;
pub const FD_MODE_UNIX: u8 = 1;
pub const FD_MODE_TRUSTY: u8 = 2;
pub const TYPE_NATIVE_FILE_DESCRIPTOR: i32 = 2;
pub fn is_supported_protocol_version(version: u32) -> bool {
version <= SUPPORTED_MAX_VERSION || version == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL
}
fn reply_fixed_len(version: u32) -> usize {
if version == PROTOCOL_V0 {
4
} else {
20
}
}
fn has_object_table(version: u32) -> bool {
version >= PROTOCOL_V1
}
fn rd_u32(buf: &[u8], off: usize) -> RpcResult<u32> {
let end = off
.checked_add(4)
.ok_or(RpcError::Protocol("offset overflow"))?;
let s = buf
.get(off..end)
.ok_or(RpcError::Protocol("truncated u32"))?;
Ok(u32::from_le_bytes(s.try_into().unwrap()))
}
fn rd_u64(buf: &[u8], off: usize) -> RpcResult<u64> {
let end = off
.checked_add(8)
.ok_or(RpcError::Protocol("offset overflow"))?;
let s = buf
.get(off..end)
.ok_or(RpcError::Protocol("truncated u64"))?;
Ok(u64::from_le_bytes(s.try_into().unwrap()))
}
#[derive(Debug, Clone, Copy)]
pub struct Android13PlusCodec {
version: u32,
}
impl Default for Android13PlusCodec {
fn default() -> Self {
Self::android13()
}
}
impl Android13PlusCodec {
pub fn android13() -> Self {
Self {
version: PROTOCOL_V0,
}
}
pub fn android14_15() -> Self {
Self {
version: PROTOCOL_V1,
}
}
pub fn android16() -> Self {
Self {
version: PROTOCOL_V2,
}
}
pub fn with_version(version: u32) -> RpcResult<Self> {
if !is_supported_protocol_version(version) {
return Err(RpcError::Protocol("unsupported RPC wire protocol version"));
}
Ok(Self { version })
}
pub fn version(&self) -> u32 {
self.version
}
fn header(command: u32, body_size: usize) -> RpcResult<[u8; WIRE_HEADER_LEN]> {
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let mut h = [0u8; WIRE_HEADER_LEN];
h[0..4].copy_from_slice(&command.to_le_bytes());
h[4..8].copy_from_slice(&(body_size as u32).to_le_bytes());
Ok(h)
}
pub(crate) fn encode_addr(addr: &RpcAddress) -> [u8; A13_ADDR_LEN] {
let mut out = [0u8; A13_ADDR_LEN];
if addr.is_zero() {
return out;
}
let raw = addr.as_wire_bytes();
let address = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
let mut options = ADDR_OPTION_CREATED;
if raw[8] == 2 {
options |= ADDR_OPTION_FOR_SERVER;
}
out[0..4].copy_from_slice(&options.to_le_bytes());
out[4..8].copy_from_slice(&address.to_le_bytes());
out
}
pub(crate) fn decode_addr(buf: &[u8], off: usize) -> RpcResult<RpcAddress> {
let options = rd_u32(buf, off)?;
let address = rd_u32(buf, off + 4)?;
if options & !(ADDR_OPTION_CREATED | ADDR_OPTION_FOR_SERVER) != 0 {
return Err(RpcError::Protocol("unknown RpcWireAddress option bit"));
}
if options == 0 && address == 0 {
return Ok(RpcAddress::zero());
}
if options & ADDR_OPTION_CREATED == 0 {
return Err(RpcError::Protocol("RpcWireAddress without CREATED"));
}
let mut bytes = [0u8; super::address::RPC_ADDR_LEN];
bytes[0..4].copy_from_slice(&address.to_le_bytes());
bytes[8] = if options & ADDR_OPTION_FOR_SERVER != 0 {
2
} else {
1
};
Ok(RpcAddress::from_wire_bytes(bytes))
}
pub fn encode_connection_header(
&self,
incoming: bool,
fd_mode: u8,
session_id: &[u8],
) -> RpcResult<Vec<u8>> {
let id_size: u16 = session_id
.len()
.try_into()
.map_err(|_| RpcError::Protocol("session_id length exceeds u16"))?;
let mut out = Vec::with_capacity(A13_CONN_HEADER_LEN + session_id.len());
out.extend_from_slice(&self.version.to_le_bytes()); out.push(if incoming { CONN_OPTION_INCOMING } else { 0 }); if self.version == PROTOCOL_V0 {
out.extend_from_slice(&[0u8; 9]); } else {
out.push(fd_mode); out.extend_from_slice(&[0u8; 8]); }
out.extend_from_slice(&id_size.to_le_bytes()); out.extend_from_slice(session_id);
Ok(out)
}
pub fn decode_connection_header(&self, buf: &[u8]) -> RpcResult<(u32, u8, u8, Vec<u8>)> {
if buf.len() < A13_CONN_HEADER_LEN {
return Err(RpcError::Protocol("RpcConnectionHeader truncated"));
}
let version = rd_u32(buf, 0)?;
let options = buf[4];
let fd_mode = if version == PROTOCOL_V0 { 0 } else { buf[5] };
if !matches!(fd_mode, FD_MODE_NONE | FD_MODE_UNIX | FD_MODE_TRUSTY) {
return Err(RpcError::Protocol(
"unknown RpcConnectionHeader.fileDescriptorTransportMode",
));
}
let id_size = u16::from_le_bytes([buf[14], buf[15]]) as usize;
let end = A13_CONN_HEADER_LEN
.checked_add(id_size)
.ok_or(RpcError::Protocol("sessionIdSize overflow"))?;
let session_id = buf
.get(A13_CONN_HEADER_LEN..end)
.ok_or(RpcError::Protocol("session id truncated"))?
.to_vec();
if buf.len() != end {
return Err(RpcError::Protocol("RpcConnectionHeader length mismatch"));
}
Ok((version, options, fd_mode, session_id))
}
pub fn encode_new_session_response(&self, version: u32) -> [u8; A13_NEW_SESSION_RESP_LEN] {
let mut out = [0u8; A13_NEW_SESSION_RESP_LEN];
out[0..4].copy_from_slice(&version.to_le_bytes());
out
}
pub fn decode_new_session_response(&self, buf: &[u8]) -> RpcResult<u32> {
if buf.len() < A13_NEW_SESSION_RESP_LEN {
return Err(RpcError::Protocol("RpcNewSessionResponse truncated"));
}
let version = rd_u32(buf, 0)?;
if !is_supported_protocol_version(version) {
return Err(RpcError::Protocol("unsupported RPC wire protocol version"));
}
Ok(version)
}
pub fn encode_connection_init(&self) -> [u8; A13_CONN_INIT_LEN] {
let mut out = [0u8; A13_CONN_INIT_LEN];
out[0..4].copy_from_slice(&CONN_INIT_OKAY);
out
}
pub fn decode_connection_init(&self, buf: &[u8]) -> RpcResult<()> {
if buf.len() < A13_CONN_INIT_LEN {
return Err(RpcError::Protocol(
"RpcOutgoingConnectionInit truncated (peer may be speaking the r34 profile)",
));
}
if buf[0..4] != CONN_INIT_OKAY {
return Err(RpcError::Protocol(
"expected RpcOutgoingConnectionInit \"cci\" — the peer may be speaking the \
r34 (default) profile, not android-13+",
));
}
Ok(())
}
}
fn encode_data_and_table(
out: &mut Vec<u8>,
version: u32,
data: &[u8],
object_positions: &[u32],
) -> RpcResult<()> {
if !has_object_table(version) {
if !object_positions.is_empty() {
return Err(RpcError::Protocol(
"v0 wire has no object table (objects need protocol version >= 1)",
));
}
out.extend_from_slice(data);
return Ok(());
}
out.extend_from_slice(data);
for pos in object_positions {
out.extend_from_slice(&pos.to_le_bytes());
}
Ok(())
}
fn split_data_and_table(
version: u32,
rest: &[u8],
parcel_data_size: usize,
) -> RpcResult<(Vec<u8>, Vec<u32>)> {
if !has_object_table(version) {
return Ok((rest.to_vec(), Vec::new()));
}
if parcel_data_size > rest.len() {
return Err(RpcError::Protocol(
"parcelDataSize larger than available bytes",
));
}
let (data, table_bytes) = rest.split_at(parcel_data_size);
if table_bytes.len() % 4 != 0 {
return Err(RpcError::Protocol(
"object table byte size not a multiple of 4",
));
}
let positions = table_bytes
.chunks_exact(4)
.map(|c| u32::from_le_bytes([c[0], c[1], c[2], c[3]]))
.collect();
Ok((data.to_vec(), positions))
}
impl WireCodec for Android13PlusCodec {
fn encode_transact(&self, txn: &WireTransaction) -> RpcResult<Vec<u8>> {
let table_bytes = if has_object_table(self.version) {
4 * txn.object_positions.len()
} else {
0
};
let body_len = A13_TXN_FIXED_LEN + txn.data.len() + table_bytes;
let header = Self::header(CMD_TRANSACT, body_len)?;
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + body_len);
out.extend_from_slice(&header);
out.extend_from_slice(&Self::encode_addr(&txn.address)); out.extend_from_slice(&txn.code.to_le_bytes()); out.extend_from_slice(&txn.flags.to_le_bytes()); out.extend_from_slice(&txn.async_number.to_le_bytes()); let parcel_size = if self.version == PROTOCOL_V0 {
0u32
} else {
txn.data.len() as u32
};
out.extend_from_slice(&parcel_size.to_le_bytes()); out.extend_from_slice(&[0u8; 12]); encode_data_and_table(&mut out, self.version, &txn.data, &txn.object_positions)?;
Ok(out)
}
fn encode_reply(&self, reply: &WireReply) -> RpcResult<Vec<u8>> {
let fixed = reply_fixed_len(self.version);
let table_bytes = if has_object_table(self.version) {
4 * reply.object_positions.len()
} else {
0
};
let body_len = fixed + reply.data.len() + table_bytes;
let header = Self::header(CMD_REPLY, body_len)?;
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + body_len);
out.extend_from_slice(&header);
out.extend_from_slice(&reply.status.to_le_bytes()); if self.version != PROTOCOL_V0 {
out.extend_from_slice(&(reply.data.len() as u32).to_le_bytes()); out.extend_from_slice(&[0u8; 12]); }
encode_data_and_table(&mut out, self.version, &reply.data, &reply.object_positions)?;
Ok(out)
}
fn encode_dec_strong(&self, addr: &RpcAddress) -> Vec<u8> {
let header = Self::header(CMD_DEC_STRONG, A13_DEC_STRONG_LEN)
.expect("DEC_STRONG body length is a const ≪ MAX_FRAME_LEN");
let mut out = Vec::with_capacity(WIRE_HEADER_LEN + A13_DEC_STRONG_LEN);
out.extend_from_slice(&header);
out.extend_from_slice(&Self::encode_addr(addr)); out.extend_from_slice(&1u32.to_le_bytes()); out.extend_from_slice(&0u32.to_le_bytes()); out
}
fn decode_message(&self, frame: &[u8]) -> RpcResult<WireMessage> {
if frame.len() < WIRE_HEADER_LEN {
return Err(RpcError::Protocol("frame shorter than RpcWireHeader"));
}
let command = rd_u32(frame, 0)?;
let body_size = rd_u32(frame, 4)? as usize;
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let expected = WIRE_HEADER_LEN
.checked_add(body_size)
.ok_or(RpcError::Protocol("body size overflow"))?;
if frame.len() != expected {
return Err(RpcError::Protocol("frame length != header + bodySize"));
}
let body = &frame[WIRE_HEADER_LEN..];
match command {
CMD_TRANSACT => {
if body.len() < A13_TXN_FIXED_LEN {
return Err(RpcError::Protocol("RpcWireTransaction truncated"));
}
let address = Self::decode_addr(body, 0)?;
let code = rd_u32(body, A13_ADDR_LEN)?;
let flags = rd_u32(body, A13_ADDR_LEN + 4)?;
let async_number = rd_u64(body, A13_ADDR_LEN + 8)?;
let parcel_data_size = rd_u32(body, A13_ADDR_LEN + 16)? as usize;
let (data, object_positions) = split_data_and_table(
self.version,
&body[A13_TXN_FIXED_LEN..],
parcel_data_size,
)?;
Ok(WireMessage::Transact(WireTransaction {
address,
code,
flags,
async_number,
data,
object_positions,
}))
}
CMD_REPLY => {
let fixed = reply_fixed_len(self.version);
if body.len() < fixed {
return Err(RpcError::Protocol("RpcWireReply truncated"));
}
let status = rd_u32(body, 0)? as i32;
let parcel_data_size = if has_object_table(self.version) {
rd_u32(body, 4)? as usize
} else {
0
};
let (data, object_positions) =
split_data_and_table(self.version, &body[fixed..], parcel_data_size)?;
Ok(WireMessage::Reply(WireReply {
status,
data,
object_positions,
}))
}
CMD_DEC_STRONG => {
if body.len() != A13_DEC_STRONG_LEN {
return Err(RpcError::Protocol("RpcDecStrong body != 16 bytes"));
}
let address = Self::decode_addr(body, 0)?;
let amount = rd_u32(body, A13_ADDR_LEN)?;
Ok(WireMessage::DecStrong(address, amount))
}
_ => Err(RpcError::Protocol("unknown RpcWireHeader.command")),
}
}
fn encode_session_preamble(&self, session_id: i32) -> Vec<u8> {
let _ = session_id;
self.encode_connection_header(false, FD_MODE_NONE, &[])
.expect("preamble passes empty session_id ⇒ u16 bound trivially satisfied")
}
fn decode_session_preamble(&self, buf: &[u8]) -> RpcResult<i32> {
Ok(self.decode_new_session_response(buf)? as i32)
}
}
fn map_io(e: std::io::Error) -> RpcError {
RpcError::from(e)
}
fn classify_short_read(e: RpcError, progress: usize) -> RpcError {
if progress > 0 && e.leaves_frame_boundary_intact() {
if matches!(e, RpcError::Timeout) {
RpcError::DeadlineMidFrame
} else {
RpcError::Truncated
}
} else {
e
}
}
fn read_exact_raw<R: Read>(r: &mut R, n: usize) -> RpcResult<Vec<u8>> {
let mut buf = vec![0u8; n];
read_exact_into(r, &mut buf)?;
Ok(buf)
}
fn read_exact_into<R: Read>(r: &mut R, buf: &mut [u8]) -> RpcResult<()> {
let n = buf.len();
let mut got = 0;
while got < n {
match r.read(&mut buf[got..]) {
Ok(0) => return Err(classify_short_read(RpcError::EndOfStream, got)),
Ok(k) => got += k,
Err(ref e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
Err(ref e) if super::transport::is_timeout(e) => {
return Err(classify_short_read(RpcError::Timeout, got))
}
Err(e) => return Err(classify_short_read(map_io(e), got)),
}
}
Ok(())
}
pub fn write_aosp_message<W: Write>(w: &mut W, msg: &[u8]) -> RpcResult<()> {
if msg.len() < WIRE_HEADER_LEN {
return Err(RpcError::Protocol("message shorter than RpcWireHeader"));
}
if msg.len() - WIRE_HEADER_LEN > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: msg.len() - WIRE_HEADER_LEN,
max: MAX_FRAME_LEN,
});
}
w.write_all(msg).map_err(map_io)?;
w.flush().map_err(map_io)?;
Ok(())
}
pub fn read_aosp_message<R: Read>(r: &mut R) -> RpcResult<Vec<u8>> {
let header = read_exact_raw(r, WIRE_HEADER_LEN)?;
let body_size = u32::from_le_bytes([header[4], header[5], header[6], header[7]]) as usize;
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let mut out = vec![0u8; WIRE_HEADER_LEN + body_size];
out[..WIRE_HEADER_LEN].copy_from_slice(&header);
if body_size > 0 {
read_exact_into(r, &mut out[WIRE_HEADER_LEN..])
.map_err(|e| classify_short_read(e, WIRE_HEADER_LEN))?;
}
Ok(out)
}
pub fn write_aosp_message_with_fds(
t: &dyn super::transport::RpcTransport,
msg: &[u8],
fds: &[std::os::fd::BorrowedFd<'_>],
) -> RpcResult<()> {
if msg.len() < WIRE_HEADER_LEN {
return Err(RpcError::Protocol("message shorter than RpcWireHeader"));
}
if msg.len() - WIRE_HEADER_LEN > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: msg.len() - WIRE_HEADER_LEN,
max: MAX_FRAME_LEN,
});
}
t.send_raw_with_fds(msg, fds)
}
pub fn read_aosp_message_with_fds(
t: &dyn super::transport::RpcTransport,
) -> RpcResult<(Vec<u8>, Vec<std::os::fd::OwnedFd>)> {
let mut fds: Vec<std::os::fd::OwnedFd> = Vec::new();
let mut total_read = 0usize;
let mut fill = |dst: &mut [u8]| -> RpcResult<()> {
let mut got = 0;
while got < dst.len() {
let (n, mut more) = match t.recv_raw_with_fds(&mut dst[got..]) {
Ok(v) => v,
Err(e) => return Err(classify_short_read(e, total_read)),
};
fds.append(&mut more);
if fds.len() > super::transport::unix::MAX_FDS_PER_FRAME {
return Err(RpcError::Protocol(
"RPC message exceeded MAX_FDS_PER_FRAME (ancillary fd budget)",
));
}
if n == 0 {
return Err(classify_short_read(RpcError::EndOfStream, total_read));
}
got += n;
total_read += n;
}
Ok(())
};
let mut header = [0u8; WIRE_HEADER_LEN];
fill(&mut header)?;
let body_size = u32::from_le_bytes([header[4], header[5], header[6], header[7]]) as usize;
if body_size > MAX_FRAME_LEN {
return Err(RpcError::FrameTooLarge {
declared: body_size,
max: MAX_FRAME_LEN,
});
}
let mut out = vec![0u8; WIRE_HEADER_LEN + body_size];
out[..WIRE_HEADER_LEN].copy_from_slice(&header);
if body_size > 0 {
fill(&mut out[WIRE_HEADER_LEN..])?;
}
Ok((out, fds))
}
pub fn client_connect<S: Read + Write>(
stream: &mut S,
max_version: u32,
incoming: bool,
fd_mode: u8,
) -> RpcResult<Android13PlusCodec> {
client_connect_with_id(stream, max_version, incoming, fd_mode, &[])
}
pub fn client_connect_with_id<S: Read + Write>(
stream: &mut S,
max_version: u32,
incoming: bool,
fd_mode: u8,
session_id: &[u8],
) -> RpcResult<Android13PlusCodec> {
let hdr_codec = Android13PlusCodec::with_version(max_version)?;
let header = hdr_codec.encode_connection_header(incoming, fd_mode, session_id)?;
write_all_raw(stream, &header)?;
let requesting_new_session = session_id.is_empty();
if incoming {
let init = read_exact_raw(stream, A13_CONN_INIT_LEN)?;
hdr_codec.decode_connection_init(&init)?;
} else {
write_all_raw(stream, &hdr_codec.encode_connection_init())?;
}
if requesting_new_session {
let resp = read_exact_raw(stream, A13_NEW_SESSION_RESP_LEN)?;
let negotiated = hdr_codec.decode_new_session_response(&resp)?;
if negotiated > max_version {
return Err(RpcError::Protocol(
"server upgraded the protocol version past the client cap",
));
}
if negotiated == hdr_codec.version() {
Ok(hdr_codec)
} else {
Android13PlusCodec::with_version(negotiated)
}
} else {
Ok(hdr_codec)
}
}
pub fn server_accept<S: Read + Write>(
stream: &mut S,
server_max_version: u32,
) -> RpcResult<(Android13PlusCodec, u8, Vec<u8>, bool)> {
let accepted = server_accept_deferred_init(stream, server_max_version)?;
if accepted.3 {
server_write_connection_init(stream, &accepted.0)?;
}
Ok(accepted)
}
pub fn server_write_connection_init<S: Write>(
stream: &mut S,
codec: &Android13PlusCodec,
) -> RpcResult<()> {
write_all_raw(stream, &codec.encode_connection_init())
}
pub fn server_accept_deferred_init<S: Read + Write>(
stream: &mut S,
server_max_version: u32,
) -> RpcResult<(Android13PlusCodec, u8, Vec<u8>, bool)> {
let head = read_exact_raw(stream, A13_CONN_HEADER_LEN)?;
let client_version = u32::from_le_bytes([head[0], head[1], head[2], head[3]]);
let options = head[4];
let incoming = (options & CONN_OPTION_INCOMING) != 0;
let id_size = u16::from_le_bytes([head[14], head[15]]) as usize;
let session_id = if id_size > 0 {
read_exact_raw(stream, id_size)?
} else {
Vec::new()
};
let fd_mode = if client_version == PROTOCOL_V0 {
FD_MODE_NONE
} else {
head[5]
};
if !matches!(fd_mode, FD_MODE_NONE | FD_MODE_UNIX | FD_MODE_TRUSTY) {
return Err(RpcError::Protocol(
"unknown RpcConnectionHeader.fileDescriptorTransportMode",
));
}
let negotiated = client_version.min(server_max_version);
let codec = Android13PlusCodec::with_version(negotiated)?;
let requesting_new_session = session_id.is_empty();
if requesting_new_session {
write_all_raw(stream, &codec.encode_new_session_response(negotiated))?;
}
if requesting_new_session && incoming {
return Err(RpcError::Protocol(
"new-session request set RPC_CONNECTION_OPTION_INCOMING",
));
}
if !incoming {
let init = read_exact_raw(stream, A13_CONN_INIT_LEN)?;
codec.decode_connection_init(&init)?;
}
Ok((codec, fd_mode, session_id, incoming))
}
fn write_all_raw<W: Write>(w: &mut W, buf: &[u8]) -> RpcResult<()> {
w.write_all(buf).map_err(map_io)?;
w.flush().map_err(map_io)?;
Ok(())
}
pub struct RawTransportIo<'a>(pub &'a dyn super::transport::RpcTransport);
impl Read for RawTransportIo<'_> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0.recv_raw(buf).map_err(std::io::Error::from)
}
}
impl Write for RawTransportIo<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.write_all(buf)?;
Ok(buf.len())
}
fn write_all(&mut self, buf: &[u8]) -> std::io::Result<()> {
self.0.send_raw(buf).map_err(std::io::Error::from)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(()) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rpc::address::RPC_SESSION_ID_NEW;
use crate::rpc::wire::{R34Codec, WIRE_TXN_FIXED_LEN};
use crate::rpc::AddressSpace;
fn txn(addr: RpcAddress, data: Vec<u8>) -> WireTransaction {
WireTransaction {
address: addr,
code: 0xdead_beef,
flags: 1,
async_number: 0x0102_0304_0506_0708,
data,
..Default::default()
}
}
fn timed_socketpair() -> (
std::os::unix::net::UnixStream,
std::os::unix::net::UnixStream,
) {
let (c, s) = std::os::unix::net::UnixStream::pair().expect("socketpair");
let timeout = std::time::Duration::from_secs(10);
for sock in [&c, &s] {
sock.set_read_timeout(Some(timeout))
.expect("set read timeout");
sock.set_write_timeout(Some(timeout))
.expect("set write timeout");
}
(c, s)
}
fn drain_leftover(stream: &mut std::os::unix::net::UnixStream) -> Vec<u8> {
use std::io::Read;
let mut out = Vec::new();
let mut chunk = [0u8; 64];
loop {
match stream.read(&mut chunk) {
Ok(0) => break,
Ok(n) => out.extend_from_slice(&chunk[..n]),
Err(e)
if matches!(
e.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::TimedOut
) =>
{
break
}
Err(e) => panic!("drain_leftover: unexpected error: {e}"),
}
}
out
}
#[test]
fn android13plus_spec_golden_vectors() {
let c0 = Android13PlusCodec::android13();
assert_eq!(c0.version(), 0);
let enc = c0
.encode_transact(&WireTransaction {
address: RpcAddress::zero(),
code: 0,
flags: 0,
async_number: 0,
data: vec![],
..Default::default()
})
.unwrap();
let mut want = Vec::new();
want.extend_from_slice(&0u32.to_le_bytes()); want.extend_from_slice(&40u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&[0u8; 40]); assert_eq!(enc, want, "v0 TRANSACT layout");
assert_eq!(A13_TXN_FIXED_LEN, 40);
let enc = c0
.encode_reply(&WireReply {
status: 0,
data: vec![0xAA, 0xBB],
..Default::default()
})
.unwrap();
let mut want = Vec::new();
want.extend_from_slice(&1u32.to_le_bytes()); want.extend_from_slice(&6u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&0i32.to_le_bytes()); want.extend_from_slice(&[0xAA, 0xBB]);
assert_eq!(enc, want, "v0 RpcWireReply = 4B fixed");
let c1 = Android13PlusCodec::android14_15();
assert_eq!(c1.version(), 1);
let data = vec![1u8, 2, 3, 4, 5];
let enc = c1
.encode_transact(&txn(RpcAddress::zero(), data.clone()))
.unwrap();
let body = &enc[WIRE_HEADER_LEN..];
assert_eq!(
&body[24..28],
&(data.len() as u32).to_le_bytes(),
"v1 RpcWireTransaction.parcelDataSize"
);
assert_eq!(&body[28..40], &[0u8; 12]); assert_eq!(&body[40..], &data[..]);
let enc = c1
.encode_reply(&WireReply {
status: 7,
data: vec![0xCC, 0xDD, 0xEE],
..Default::default()
})
.unwrap();
let mut want = Vec::new();
want.extend_from_slice(&1u32.to_le_bytes()); want.extend_from_slice(&23u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&7i32.to_le_bytes()); want.extend_from_slice(&3u32.to_le_bytes()); want.extend_from_slice(&[0u8; 12]); want.extend_from_slice(&[0xCC, 0xDD, 0xEE]);
assert_eq!(enc, want, "v1 RpcWireReply = 20B fixed");
for c in [c0, c1] {
let mut ctr = 0u64;
let a = RpcAddress::unique(&mut ctr, AddressSpace::Initiator);
let enc = c.encode_dec_strong(&a);
let mut want = Vec::new();
want.extend_from_slice(&2u32.to_le_bytes()); want.extend_from_slice(&16u32.to_le_bytes()); want.extend_from_slice(&[0u8; 8]); want.extend_from_slice(&ADDR_OPTION_CREATED.to_le_bytes()); want.extend_from_slice(&1u32.to_le_bytes()); want.extend_from_slice(&1u32.to_le_bytes()); want.extend_from_slice(&0u32.to_le_bytes()); assert_eq!(enc, want, "DEC_STRONG layout (v{})", c.version());
}
let h0 = c0
.encode_connection_header(false, FD_MODE_UNIX, &[])
.unwrap();
assert_eq!(h0.len(), A13_CONN_HEADER_LEN);
assert_eq!(&h0[0..4], &0u32.to_le_bytes()); assert_eq!(h0[5], 0, "v0 byte 5 is reserved, not fdMode");
let h1 = c1
.encode_connection_header(false, FD_MODE_UNIX, &[])
.unwrap();
assert_eq!(&h1[0..4], &1u32.to_le_bytes()); assert_eq!(h1[5], FD_MODE_UNIX, "v1 fileDescriptorTransportMode");
assert_eq!(&h1[14..16], &0u16.to_le_bytes()); let h1i = c1
.encode_connection_header(true, FD_MODE_NONE, &[])
.unwrap();
assert_eq!(h1i[4], CONN_OPTION_INCOMING);
assert_eq!(
c1.decode_connection_header(&h1).unwrap(),
(1, 0, FD_MODE_UNIX, Vec::new())
);
assert_eq!(
c0.decode_connection_header(&h0).unwrap(),
(0, 0, 0, Vec::new())
);
let r = c1.encode_new_session_response(PROTOCOL_V1);
assert_eq!(c1.decode_new_session_response(&r).unwrap(), 1);
let init = c1.encode_connection_init();
assert_eq!(&init[0..4], b"cci\0");
c1.decode_connection_init(&init).expect("\"cci\"");
for bad in [&[0u8; 8][..], &[0u8; 2][..]] {
let why = match c1.decode_connection_init(bad) {
Err(RpcError::Protocol(why)) => why,
other => panic!("expected a protocol reject, got {other:?}"),
};
assert!(
why.contains("r34"),
"the \"cci\" reject must name the r34 profile: {why}"
);
}
assert!(is_supported_protocol_version(0));
assert!(is_supported_protocol_version(1));
assert!(is_supported_protocol_version(2));
assert!(is_supported_protocol_version(
RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL
));
assert!(!is_supported_protocol_version(3));
assert!(!is_supported_protocol_version(
RPC_WIRE_PROTOCOL_VERSION_NEXT
));
assert!(Android13PlusCodec::with_version(3).is_err());
assert_eq!(Android13PlusCodec::with_version(2).unwrap().version(), 2);
assert_eq!(SUPPORTED_MAX_VERSION, 2);
assert_eq!(RPC_WIRE_PROTOCOL_VERSION_NEXT, 3);
}
#[test]
fn android13plus_roundtrip_all_commands() {
for c in [
Android13PlusCodec::android13(),
Android13PlusCodec::android14_15(),
Android13PlusCodec::android16(),
] {
for size in [0usize, 1, 17, 4096, 1 << 20] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let positions: Vec<u32> = if c.version() >= PROTOCOL_V1 && size >= 8 {
vec![0, (size / 2) as u32, (size - 4) as u32]
} else {
Vec::new()
};
for space in [AddressSpace::Initiator, AddressSpace::Acceptor] {
let mut ctr = 0x4142u64;
let addr = RpcAddress::unique(&mut ctr, space);
let mut t = txn(addr, data.clone());
t.object_positions = positions.clone();
match c.decode_message(&c.encode_transact(&t).unwrap()).unwrap() {
WireMessage::Transact(d) => {
assert_eq!(d.address, addr, "addr rt (v{}, {space:?})", c.version());
assert_eq!(d.code, 0xdead_beef);
assert_eq!(d.flags, 1);
assert_eq!(d.async_number, 0x0102_0304_0506_0708);
assert_eq!(d.data, data);
assert_eq!(
d.object_positions,
positions,
"object table rt (v{})",
c.version()
);
}
other => panic!("expected Transact, got {other:?}"),
}
}
match c
.decode_message(
&c.encode_reply(&WireReply {
status: -42,
data: data.clone(),
object_positions: positions.clone(),
})
.unwrap(),
)
.unwrap()
{
WireMessage::Reply(r) => {
assert_eq!(r.status, -42, "v{}", c.version());
assert_eq!(r.data, data);
assert_eq!(r.object_positions, positions, "reply table rt");
}
other => panic!("expected Reply, got {other:?}"),
}
}
let mut ctr = 9u64;
let addr = RpcAddress::unique(&mut ctr, AddressSpace::Initiator);
match c.decode_message(&c.encode_dec_strong(&addr)).unwrap() {
WireMessage::DecStrong(a, amount) => {
assert_eq!(a, addr);
assert_eq!(amount, 1, "rsbinder emits amount = 1 per DEC_STRONG");
}
other => panic!("expected DecStrong, got {other:?}"),
}
let mut framed = c.encode_dec_strong(&addr);
let amt_off = WIRE_HEADER_LEN + A13_ADDR_LEN;
framed[amt_off..amt_off + 4].copy_from_slice(&3u32.to_le_bytes());
match c.decode_message(&framed).unwrap() {
WireMessage::DecStrong(a, amount) => {
assert_eq!(a, addr);
assert_eq!(amount, 3, "decoder must honor RpcDecStrong.amount");
}
other => panic!("expected DecStrong, got {other:?}"),
}
}
}
#[test]
fn android13plus_distinct_and_r34_unchanged() {
let t = WireTransaction {
address: RpcAddress::zero(),
code: 0,
flags: 0,
async_number: 0,
data: vec![],
..Default::default()
};
let r34 = R34Codec.encode_transact(&t).unwrap();
assert_eq!(r34.len(), WIRE_HEADER_LEN + WIRE_TXN_FIXED_LEN); assert_eq!(&r34[4..8], &64u32.to_le_bytes());
let v0 = Android13PlusCodec::android13().encode_transact(&t).unwrap();
let v1 = Android13PlusCodec::android14_15()
.encode_transact(&t)
.unwrap();
let v2 = Android13PlusCodec::android16().encode_transact(&t).unwrap();
assert_eq!(v0.len(), WIRE_HEADER_LEN + 40);
assert_eq!(v1.len(), WIRE_HEADER_LEN + 40);
assert_ne!(v0, r34, "android-13+ must differ from r34");
assert_eq!(v1, v2, "no-object v1 ≡ v2 (AC-8.2)");
let rep = WireReply {
status: 0,
data: vec![],
..Default::default()
};
assert_eq!(
Android13PlusCodec::android13()
.encode_reply(&rep)
.unwrap()
.len(),
WIRE_HEADER_LEN + 4
);
assert_eq!(
Android13PlusCodec::android14_15()
.encode_reply(&rep)
.unwrap()
.len(),
WIRE_HEADER_LEN + 20
);
assert_eq!(
Android13PlusCodec::android14_15()
.encode_reply(&rep)
.unwrap(),
Android13PlusCodec::android16().encode_reply(&rep).unwrap(),
"no-object reply v1 ≡ v2 (AC-8.2)"
);
}
#[test]
fn android13plus_decoder_rejects_hostile_input_safely() {
for c in [
Android13PlusCodec::android13(),
Android13PlusCodec::android14_15(),
Android13PlusCodec::android16(),
] {
assert!(c.decode_message(&[]).is_err());
assert!(c.decode_message(&[0u8; 15]).is_err());
let mut f = Vec::new();
f.extend_from_slice(&0u32.to_le_bytes());
f.extend_from_slice(&u32::MAX.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
assert!(matches!(
c.decode_message(&f),
Err(RpcError::FrameTooLarge { .. })
));
let mut f = Vec::new();
f.extend_from_slice(&0u32.to_le_bytes());
f.extend_from_slice(&20u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&[0u8; 20]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
let mut f = Vec::new();
f.extend_from_slice(&2u32.to_le_bytes());
f.extend_from_slice(&8u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&[0u8; 8]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
let mut f = Vec::new();
f.extend_from_slice(&999u32.to_le_bytes());
f.extend_from_slice(&0u32.to_le_bytes());
f.extend_from_slice(&[0u8; 8]);
assert!(matches!(c.decode_message(&f), Err(RpcError::Protocol(_))));
assert!(c.decode_connection_header(&[0u8; 8]).is_err());
assert!(c.decode_new_session_response(&[0u8; 4]).is_err());
assert!(c.decode_connection_init(&[0u8; 4]).is_err());
let pre = c.encode_session_preamble(RPC_SESSION_ID_NEW);
assert_eq!(pre.len(), A13_CONN_HEADER_LEN);
assert_eq!(
c.decode_session_preamble(&c.encode_new_session_response(0))
.unwrap(),
0
);
}
let c1 = Android13PlusCodec::android14_15();
let mut f = Vec::new();
f.extend_from_slice(&1u32.to_le_bytes()); f.extend_from_slice(&4u32.to_le_bytes()); f.extend_from_slice(&[0u8; 8]);
f.extend_from_slice(&[0u8; 4]);
assert!(matches!(c1.decode_message(&f), Err(RpcError::Protocol(_))));
}
#[test]
fn android13plus_live_protocol_e2e_over_raw_socket() {
use std::thread;
for (cmax, smax, expect) in [
(0u32, 0u32, 0u32),
(1, 1, 1),
(1, 0, 0),
(0, 1, 0),
(2, 2, 2),
(2, 1, 1),
(1, 2, 1),
(2, 0, 0),
] {
let (mut c, mut s) = timed_socketpair();
let srv = thread::spawn(move || -> u32 {
let (codec, fd_mode, sid, incoming) =
server_accept(&mut s, smax).expect("server_accept");
assert_eq!(fd_mode, FD_MODE_NONE);
assert!(sid.is_empty(), "new-session ⇒ empty session id");
assert!(!incoming, "new-session is always outgoing");
let raw = read_aosp_message(&mut s).expect("read transact");
match codec.decode_message(&raw).expect("decode transact") {
WireMessage::Transact(t) => {
assert!(t.address.is_zero());
assert_eq!(t.code, 0); assert!(t.data.is_empty());
}
other => panic!("expected Transact, got {other:?}"),
}
write_aosp_message(
&mut s,
&codec
.encode_reply(&WireReply {
status: 0,
data: b"root!".to_vec(),
..Default::default()
})
.unwrap(),
)
.expect("write reply");
let raw = read_aosp_message(&mut s).expect("read dec_strong");
match codec.decode_message(&raw).expect("decode dec_strong") {
WireMessage::DecStrong(..) => {}
other => panic!("expected DecStrong, got {other:?}"),
}
codec.version()
});
let codec = client_connect(&mut c, cmax, false, FD_MODE_NONE).expect("client_connect");
assert_eq!(codec.version(), expect, "negotiated min({cmax},{smax})");
let txn = WireTransaction {
address: RpcAddress::zero(),
code: 0,
flags: 0,
async_number: 0,
data: vec![],
..Default::default()
};
let encoded = codec.encode_transact(&txn).unwrap();
assert_eq!(&encoded[0..4], &0u32.to_le_bytes());
assert_eq!(
encoded.len(),
WIRE_HEADER_LEN + A13_TXN_FIXED_LEN,
"v{} GET_ROOT = 16 + 40, no length prefix",
codec.version()
);
write_aosp_message(&mut c, &encoded).expect("write transact");
let raw = read_aosp_message(&mut c).expect("read reply");
match codec.decode_message(&raw).expect("decode reply") {
WireMessage::Reply(r) => {
assert_eq!(r.status, 0);
assert_eq!(r.data, b"root!");
}
other => panic!("expected Reply, got {other:?}"),
}
let mut ctr = 7u64;
let addr = RpcAddress::unique(&mut ctr, AddressSpace::Initiator);
write_aosp_message(&mut c, &codec.encode_dec_strong(&addr)).expect("write dec_strong");
assert_eq!(srv.join().expect("server thread"), expect);
}
}
#[test]
fn android13plus_e2e_over_unix_transport_bridge() {
use crate::rpc::transport::UnixTransport;
use std::thread;
for (cmax, expect) in [(0u32, 0u32), (1, 1), (2, 2)] {
let (a, b) = UnixTransport::pair().expect("unix pair");
let srv = thread::spawn(move || -> u32 {
let mut io = RawTransportIo(&b);
let (codec, fd, sid, incoming) = server_accept(&mut io, 2).expect("server_accept");
assert_eq!(fd, FD_MODE_NONE);
assert!(sid.is_empty());
assert!(!incoming);
let raw = read_aosp_message(&mut io).expect("read transact");
match codec.decode_message(&raw).expect("decode") {
WireMessage::Transact(t) => assert_eq!(t.code, 0),
o => panic!("expected Transact, got {o:?}"),
}
write_aosp_message(
&mut io,
&codec
.encode_reply(&WireReply {
status: 0,
data: b"via-transport".to_vec(),
..Default::default()
})
.unwrap(),
)
.expect("write reply");
codec.version()
});
let mut io = RawTransportIo(&a);
let codec = client_connect(&mut io, cmax, false, FD_MODE_NONE).expect("client_connect");
assert_eq!(codec.version(), expect);
write_aosp_message(
&mut io,
&codec
.encode_transact(&WireTransaction {
address: RpcAddress::zero(),
code: 0,
flags: 0,
async_number: 0,
data: vec![],
..Default::default()
})
.unwrap(),
)
.expect("write transact");
let raw = read_aosp_message(&mut io).expect("read reply");
match codec.decode_message(&raw).expect("decode reply") {
WireMessage::Reply(r) => {
assert_eq!(r.status, 0);
assert_eq!(r.data, b"via-transport");
}
o => panic!("expected Reply, got {o:?}"),
}
assert_eq!(srv.join().expect("server thread"), expect);
}
use crate::rpc::transport::RpcTransport;
let (m, _m2) = crate::rpc::transport::MemTransport::pair();
assert!(m.send_raw(b"x").is_err(), "mem has no raw byte access");
assert!(m.recv_raw(&mut [0u8; 4]).is_err());
}
#[test]
fn android13plus_attach_handshake_wire_byte_exact() {
use std::net::Shutdown;
use std::thread;
let session_id = [0x42u8; 32];
for (incoming, version) in [
(false, PROTOCOL_V0),
(false, PROTOCOL_V1),
(false, PROTOCOL_V2),
(true, PROTOCOL_V0),
(true, PROTOCOL_V1),
(true, PROTOCOL_V2),
] {
let (mut c, mut s) = timed_socketpair();
let sid = session_id;
let srv = thread::spawn(move || -> (u32, bool, Vec<u8>, Vec<u8>) {
let (codec, _fd, got_sid, srv_incoming) =
server_accept(&mut s, version).expect("server_accept");
s.shutdown(Shutdown::Write).expect("srv shutdown w");
let leftover = drain_leftover(&mut s);
(codec.version(), srv_incoming, got_sid, leftover)
});
let codec = client_connect_with_id(&mut c, version, incoming, FD_MODE_NONE, &sid)
.expect("client_connect_with_id");
c.shutdown(Shutdown::Write).expect("cli shutdown w");
let client_leftover = drain_leftover(&mut c);
assert!(
client_leftover.is_empty(),
"(attach, incoming={incoming}, v={version}): server wrote {} unexpected \
byte(s) after handshake — regression of NewSessionResponse-on-attach",
client_leftover.len()
);
let (sv, sincoming, ssid, srv_leftover) = srv.join().expect("srv");
assert_eq!(sv, version);
assert_eq!(sincoming, incoming);
assert_eq!(ssid, sid.to_vec());
assert_eq!(codec.version(), version);
assert!(
srv_leftover.is_empty(),
"(attach, incoming={incoming}, v={version}): client wrote {} unexpected \
byte(s) after handshake",
srv_leftover.len()
);
}
}
#[test]
fn android13plus_new_session_incoming_rejected() {
use std::thread;
let (mut c, mut s) = timed_socketpair();
let srv = thread::spawn(move || -> RpcResult<_> {
let result = server_accept(&mut s, PROTOCOL_V1);
drop(s);
result
});
let codec = Android13PlusCodec::with_version(PROTOCOL_V1).unwrap();
let header = codec
.encode_connection_header(true, FD_MODE_NONE, &[])
.unwrap();
write_all_raw(&mut c, &header).expect("write header");
let r = srv.join().expect("srv");
assert!(
matches!(r, Err(RpcError::Protocol(_))),
"server must reject new-session + incoming, got {r:?}"
);
let buf = drain_leftover(&mut c);
assert_eq!(
buf.len(),
A13_NEW_SESSION_RESP_LEN,
"AOSP order: write RpcNewSessionResponse, then close on \
incoming. Got {} bytes: {buf:?}",
buf.len()
);
}
#[test]
fn android16_v2_version_constants_golden() {
assert_eq!(PROTOCOL_V0, 0);
assert_eq!(PROTOCOL_V1, 1);
assert_eq!(PROTOCOL_V2, 2);
assert_eq!(
SUPPORTED_MAX_VERSION, 2,
"android-16 RPC_WIRE_PROTOCOL_VERSION"
);
assert_eq!(RPC_WIRE_PROTOCOL_VERSION_NEXT, 3);
assert_eq!(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL, 0xF000_0000);
for v in [0, 1, 2] {
assert!(is_supported_protocol_version(v));
assert_eq!(Android13PlusCodec::with_version(v).unwrap().version(), v);
}
assert!(is_supported_protocol_version(
RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL
));
for v in [3u32, 4, 100, RPC_WIRE_PROTOCOL_VERSION_NEXT] {
assert!(!is_supported_protocol_version(v), "reject v{v}");
assert!(Android13PlusCodec::with_version(v).is_err());
}
assert_eq!(Android13PlusCodec::android16().version(), 2);
assert_eq!(reply_fixed_len(0), 4);
assert_eq!(reply_fixed_len(1), 20);
assert_eq!(reply_fixed_len(2), 20);
assert!(!has_object_table(0));
assert!(has_object_table(1));
assert!(has_object_table(2));
}
#[test]
fn android16_no_object_v1_eq_v2_byte_identical() {
let v1 = Android13PlusCodec::android14_15();
let v2 = Android13PlusCodec::android16();
for size in [0usize, 1, 4, 5, 64, 4096] {
let data: Vec<u8> = (0..size).map(|i| (i % 251) as u8).collect();
let mut ctr = 0xABCDu64;
let addr = RpcAddress::unique(&mut ctr, AddressSpace::Initiator);
let t = txn(addr, data.clone()); assert_eq!(
v1.encode_transact(&t).unwrap(),
v2.encode_transact(&t).unwrap(),
"no-object TRANSACT v1≡v2 (size {size})"
);
let r = WireReply {
status: -7,
data: data.clone(),
object_positions: Vec::new(),
};
assert_eq!(
v1.encode_reply(&r).unwrap(),
v2.encode_reply(&r).unwrap(),
"no-object REPLY v1≡v2 (size {size})"
);
}
}
#[test]
fn android16_v2_object_table_framing_golden() {
let c = Android13PlusCodec::android16();
let data = vec![0x11u8, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88];
let positions = vec![0u32, 4];
let t = WireTransaction {
address: RpcAddress::zero(),
code: 0xCAFE,
flags: 0,
async_number: 0,
data: data.clone(),
object_positions: positions.clone(),
};
let enc = c.encode_transact(&t).unwrap();
assert_eq!(&enc[4..8], &56u32.to_le_bytes(), "bodySize = 40+pds+4N");
let body = &enc[WIRE_HEADER_LEN..];
assert_eq!(&body[24..28], &(data.len() as u32).to_le_bytes());
assert_eq!(&body[40..48], &data[..]);
assert_eq!(&body[48..52], &0u32.to_le_bytes());
assert_eq!(&body[52..56], &4u32.to_le_bytes());
assert_eq!(enc.len(), WIRE_HEADER_LEN + 56);
match c.decode_message(&enc).unwrap() {
WireMessage::Transact(d) => {
assert_eq!(d.data, data);
assert_eq!(d.object_positions, positions);
}
o => panic!("expected Transact, got {o:?}"),
}
let r = WireReply {
status: 0,
data: data.clone(),
object_positions: positions.clone(),
};
let enc = c.encode_reply(&r).unwrap();
assert_eq!(&enc[4..8], &36u32.to_le_bytes());
let body = &enc[WIRE_HEADER_LEN..];
assert_eq!(&body[4..8], &(data.len() as u32).to_le_bytes()); assert_eq!(&body[20..28], &data[..]);
assert_eq!(&body[28..32], &0u32.to_le_bytes());
assert_eq!(&body[32..36], &4u32.to_le_bytes());
match c.decode_message(&enc).unwrap() {
WireMessage::Reply(d) => {
assert_eq!(d.data, data);
assert_eq!(d.object_positions, positions);
}
o => panic!("expected Reply, got {o:?}"),
}
let mut bad = c
.encode_transact(&WireTransaction {
address: RpcAddress::zero(),
code: 1,
flags: 0,
async_number: 0,
data: vec![1, 2, 3, 4],
object_positions: vec![0],
})
.unwrap();
let pds_off = WIRE_HEADER_LEN + 24;
bad[pds_off..pds_off + 4].copy_from_slice(&0xFFFF_FFFFu32.to_le_bytes());
assert!(matches!(c.decode_message(&bad), Err(RpcError::Protocol(_))));
let mut bad = c
.encode_transact(&WireTransaction {
address: RpcAddress::zero(),
code: 1,
flags: 0,
async_number: 0,
data: vec![0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x01],
object_positions: Vec::new(),
})
.unwrap();
bad[pds_off..pds_off + 4].copy_from_slice(&4u32.to_le_bytes());
assert!(matches!(c.decode_message(&bad), Err(RpcError::Protocol(_))));
}
#[test]
fn android16_v0_and_r34_reject_object_positions() {
let t = WireTransaction {
address: RpcAddress::zero(),
code: 1,
flags: 0,
async_number: 0,
data: vec![1, 2, 3, 4],
object_positions: vec![0],
};
let r = WireReply {
status: 0,
data: vec![1, 2, 3, 4],
object_positions: vec![0],
};
let v0 = Android13PlusCodec::android13();
assert!(matches!(v0.encode_transact(&t), Err(RpcError::Protocol(_))));
assert!(matches!(v0.encode_reply(&r), Err(RpcError::Protocol(_))));
assert!(matches!(
R34Codec.encode_transact(&t),
Err(RpcError::Protocol(_))
));
assert!(matches!(
R34Codec.encode_reply(&r),
Err(RpcError::Protocol(_))
));
let t0 = txn(RpcAddress::zero(), vec![9, 9, 9, 9]);
assert!(v0.encode_transact(&t0).is_ok());
assert!(R34Codec.encode_transact(&t0).is_ok());
}
}