blob: 304817bccbdeb65d1f315a1d4867a7004326c6b7 [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use bindings::encoding::{
Bits, Context, DataHeader, DataHeaderValue, MojomNumeric, DATA_HEADER_SIZE,
};
use bindings::mojom::{MojomEncodable, MOJOM_NULL_POINTER, UNION_SIZE};
use bindings::util;
use std::mem;
use std::ptr;
use std::vec::Vec;
use system;
use system::{CastHandle, Handle, UntypedHandle};
#[derive(Debug, Eq, PartialEq)]
pub enum ValidationError {
DifferentSizedArraysInMap,
IllegalHandle,
IllegalMemoryRange,
IllegalPointer,
MessageHeaderInvalidFlags,
MessageHeaderMissingRequestId,
MessageHeaderUnknownMethod,
MisalignedObject,
UnexpectedArrayHeader,
UnexpectedInvalidHandle,
UnexpectedNullPointer,
UnexpectedNullUnion,
UnexpectedStructHeader,
}
impl ValidationError {
pub fn as_str(self) -> &'static str {
match self {
ValidationError::DifferentSizedArraysInMap => {
"VALIDATION_ERROR_DIFFERENT_SIZED_ARRAYS_IN_MAP"
}
ValidationError::IllegalHandle => "VALIDATION_ERROR_ILLEGAL_HANDLE",
ValidationError::IllegalMemoryRange => "VALIDATION_ERROR_ILLEGAL_MEMORY_RANGE",
ValidationError::IllegalPointer => "VALIDATION_ERROR_ILLEGAL_POINTER",
ValidationError::MessageHeaderInvalidFlags => {
"VALIDATION_ERROR_MESSAGE_HEADER_INVALID_FLAGS"
}
ValidationError::MessageHeaderMissingRequestId => {
"VALIDATION_ERROR_MESSAGE_HEADER_MISSING_REQUEST_ID"
}
ValidationError::MessageHeaderUnknownMethod => {
"VALIDATION_ERROR_MESSAGE_HEADER_UNKNOWN_METHOD"
}
ValidationError::MisalignedObject => "VALIDATION_ERROR_MISALIGNED_OBJECT",
ValidationError::UnexpectedArrayHeader => "VALIDATION_ERROR_UNEXPECTED_ARRAY_HEADER",
ValidationError::UnexpectedInvalidHandle => {
"VALIDATION_ERROR_UNEXPECTED_INVALID_HANDLE"
}
ValidationError::UnexpectedNullPointer => "VALIDATION_ERROR_UNEXPECTED_NULL_POINTER",
ValidationError::UnexpectedNullUnion => "VALIDATION_ERROR_UNEXPECTED_NULL_UNION",
ValidationError::UnexpectedStructHeader => "VALIDATION_ERROR_UNEXPECTED_STRUCT_HEADER",
}
}
}
/// An decoding state represents the decoding logic for a single
/// Mojom object that is NOT inlined, such as a struct or an array.
pub struct DecodingState<'slice> {
/// The buffer the state may write to.
data: &'slice [u8],
/// The offset of this serialized object into the overall buffer.
global_offset: usize,
/// The current offset within 'data'.
offset: usize,
/// The current bit offset within 'data'.
bit_offset: Bits,
}
impl<'slice> DecodingState<'slice> {
/// Create a new decoding state.
pub fn new(buffer: &'slice [u8], offset: usize) -> DecodingState<'slice> {
DecodingState { data: buffer, global_offset: offset, offset: 0, bit_offset: Bits(0) }
}
/// Align the decoding state to the next byte.
pub fn align_to_byte(&mut self) {
if self.bit_offset > Bits(0) {
self.offset += 1;
self.bit_offset = Bits(0);
}
}
/// Align the decoding state to the next 'bytes' boundary.
pub fn align_to_bytes(&mut self, bytes: usize) {
if self.offset != 0 {
self.offset = util::align_bytes(self.offset, bytes);
}
}
/// Read a primitive from the buffer without incrementing the offset.
fn read_in_place<T: MojomNumeric>(&mut self) -> T {
let mut value: T = Default::default();
debug_assert!(mem::size_of::<T>() + self.offset <= self.data.len());
let ptr = (&self.data[self.offset..]).as_ptr();
unsafe {
ptr::copy_nonoverlapping(
mem::transmute::<*const u8, *const T>(ptr),
&mut value as *mut T,
1,
);
}
value
}
/// Read a primitive from the buffer and increment the offset.
fn read<T: MojomNumeric>(&mut self) -> T {
let value = self.read_in_place::<T>();
self.bit_offset = Bits(0);
self.offset += mem::size_of::<T>();
value
}
/// Decode a primitive from the buffer, naturally aligning before we read.
pub fn decode<T: MojomNumeric>(&mut self) -> T {
self.align_to_byte();
self.align_to_bytes(mem::size_of::<T>());
self.read::<T>()
}
/// Decode a boolean value from the buffer as one bit.
pub fn decode_bool(&mut self) -> bool {
let offset = self.offset;
// Check the bit by getting the set bit and checking if its non-zero
let value = (self.data[offset] & self.bit_offset.as_set_bit()) > 0;
self.bit_offset += Bits(1);
let (bits, bytes) = self.bit_offset.as_bits_and_bytes();
self.offset += bytes;
self.bit_offset = bits;
value
}
/// If we encounter a null pointer, increment past it.
///
/// Returns if we skipped or not.
pub fn skip_if_null_pointer(&mut self) -> bool {
self.align_to_byte();
self.align_to_bytes(8);
let ptr = self.read_in_place::<u64>();
if ptr == MOJOM_NULL_POINTER {
self.offset += 8;
}
(ptr == MOJOM_NULL_POINTER)
}
/// If we encounter a null union, increment past it.
///
/// Returns if we skipped or not.
pub fn skip_if_null_union(&mut self) -> bool {
self.align_to_byte();
self.align_to_bytes(8);
let size = self.read_in_place::<u32>();
if size == 0 {
self.offset += UNION_SIZE;
}
(size == 0)
}
/// If we encounter a null handle, increment past it.
///
/// Returns if we skipped or not.
pub fn skip_if_null_handle(&mut self) -> bool {
self.align_to_byte();
self.align_to_bytes(4);
let index = self.read_in_place::<i32>();
if index < 0 {
self.offset += 4;
}
(index < 0)
}
/// If we encounter a null interface, increment past it.
///
/// Returns if we skipped or not.
pub fn skip_if_null_interface(&mut self) -> bool {
self.align_to_byte();
self.align_to_bytes(4);
let index = self.read_in_place::<i32>();
if index < 0 {
self.offset += 8;
}
(index < 0)
}
/// Decode a pointer from the buffer as a global offset into the buffer.
///
/// The pointer in the buffer is an offset relative to the pointer to another
/// location in the buffer. We convert that to an absolute offset with respect
/// to the buffer before returning. This is our defintion of a pointer.
pub fn decode_pointer(&mut self) -> Option<u64> {
self.align_to_byte();
self.align_to_bytes(8);
let current_location = (self.global_offset + self.offset) as u64;
let offset = self.read::<u64>();
if offset == MOJOM_NULL_POINTER {
Some(MOJOM_NULL_POINTER)
} else {
offset.checked_add(current_location)
}
}
/// A routine for decoding an array header.
///
/// Must be called with offset zero (that is, it must be the first thing
/// decoded). Performs numerous validation checks.
pub fn decode_array_header<T>(&mut self) -> Result<DataHeader, ValidationError>
where
T: MojomEncodable,
{
debug_assert_eq!(self.offset, 0);
// Make sure we can read the size first...
if self.data.len() < mem::size_of::<u32>() {
return Err(ValidationError::UnexpectedArrayHeader);
}
let bytes = self.decode::<u32>();
if (bytes as usize) < DATA_HEADER_SIZE {
return Err(ValidationError::UnexpectedArrayHeader);
}
let elems = self.decode::<u32>();
match T::embed_size(&Default::default()).checked_mul(elems as usize) {
Some(value) => {
if (bytes as usize) < value.as_bytes() + DATA_HEADER_SIZE {
return Err(ValidationError::UnexpectedArrayHeader);
}
}
None => return Err(ValidationError::UnexpectedArrayHeader),
}
Ok(DataHeader::new(bytes as usize, DataHeaderValue::Elements(elems)))
}
/// A routine for decoding an struct header.
///
/// Must be called with offset zero (that is, it must be the first thing
/// decoded). Performs numerous validation checks.
pub fn decode_struct_header(
&mut self,
versions: &[(u32, u32)],
) -> Result<DataHeader, ValidationError> {
debug_assert_eq!(self.offset, 0);
// Make sure we can read the size first...
if self.data.len() < mem::size_of::<u32>() {
return Err(ValidationError::UnexpectedStructHeader);
}
let bytes = self.decode::<u32>();
if (bytes as usize) < DATA_HEADER_SIZE {
return Err(ValidationError::UnexpectedStructHeader);
}
let version = self.decode::<u32>();
// Versioning validation: versions are generated as a sorted array of tuples, so
// to find the version we are given by the header we use a binary search.
match versions.binary_search_by(|val| val.0.cmp(&version)) {
Ok(idx) => {
let (_, size) = versions[idx];
if bytes != size {
return Err(ValidationError::UnexpectedStructHeader);
}
}
Err(idx) => {
if idx == 0 {
panic!(
"Should be earliest version? \
Versions: {:?}, \
Version: {}, \
Size: {}",
versions, version, bytes
);
}
let len = versions.len();
let (latest_version, _) = versions[len - 1];
let (_, size) = versions[idx - 1];
// If this is higher than any version we know, its okay for the size to be bigger,
// but if its a version we know about, it must match the size.
if (version > latest_version && bytes < size)
|| (version <= latest_version && bytes != size)
{
return Err(ValidationError::UnexpectedStructHeader);
}
}
}
Ok(DataHeader::new(bytes as usize, DataHeaderValue::Version(version)))
}
}
/// A struct that will encode a given Mojom object and convert it into
/// bytes and a vector of handles.
pub struct Decoder<'slice> {
bytes: usize,
buffer: Option<&'slice [u8]>,
states: Vec<DecodingState<'slice>>,
handles: Vec<UntypedHandle>,
handles_claimed: usize, // A length that claims all handles were claimed up to this index
max_offset: usize, // Represents the maximum value an offset may have
}
impl<'slice> Decoder<'slice> {
/// Create a new Decoder.
pub fn new(buffer: &'slice [u8], handles: Vec<UntypedHandle>) -> Decoder<'slice> {
let max_offset = buffer.len();
Decoder {
bytes: 0,
buffer: Some(buffer),
states: Vec::new(),
handles: handles,
handles_claimed: 0,
max_offset: max_offset,
}
}
/// Claim space in the buffer to start decoding some object.
///
/// Creates a new decoding state for the object and returns a context.
pub fn claim(&mut self, offset: usize) -> Result<Context, ValidationError> {
// Check if the layout order is sane
if offset < self.bytes {
return Err(ValidationError::IllegalMemoryRange);
}
// Check for 8-byte alignment
if offset & 7 != 0 {
return Err(ValidationError::MisalignedObject);
}
// Bounds check on offset
if offset > self.max_offset {
return Err(ValidationError::IllegalPointer);
}
let mut buffer = self.buffer.take().expect("No buffer?");
let space = offset - self.bytes;
buffer = &buffer[space..];
// Make sure we can even read the bytes in the header
if buffer.len() < mem::size_of::<u32>() {
return Err(ValidationError::IllegalMemoryRange);
}
// Read the number of bytes in the memory region according to the data header
let mut read_size: u32 = 0;
unsafe {
ptr::copy_nonoverlapping(
mem::transmute::<*const u8, *const u32>(buffer.as_ptr()),
&mut read_size as *mut u32,
mem::size_of::<u32>(),
);
}
let size = u32::from_le(read_size) as usize;
// Make sure the size we read is sane...
if size > buffer.len() {
return Err(ValidationError::IllegalMemoryRange);
}
// TODO(mknyszek): Check size for validation
let (claimed, unclaimed) = buffer.split_at(size);
self.states.push(DecodingState::new(claimed, offset));
self.buffer = Some(unclaimed);
self.bytes += space + size;
Ok(Context::new(self.states.len() - 1))
}
/// Claims a handle at some particular index in the given handles array.
///
/// Returns the handle with all type information in-tact.
pub fn claim_handle<T: Handle + CastHandle>(
&mut self,
index: i32,
) -> Result<T, ValidationError> {
let real_index = if index >= 0 {
index as usize
} else {
return Err(ValidationError::UnexpectedInvalidHandle);
};
// If the index exceeds our number of handles or if we have already claimed that handle
if real_index >= self.handles.len() || real_index < self.handles_claimed {
return Err(ValidationError::IllegalHandle);
}
self.handles_claimed = real_index + 1;
let raw_handle = self.handles[real_index].get_native_handle();
unsafe {
self.handles[real_index].invalidate();
Ok(T::from_untyped(system::acquire(raw_handle)))
}
}
/// Immutably borrow a decoding state via Context.
pub fn get(&self, context: &Context) -> &DecodingState<'slice> {
&self.states[context.id()]
}
/// Mutably borrow a decoding state via Context.
pub fn get_mut(&mut self, context: &Context) -> &mut DecodingState<'slice> {
&mut self.states[context.id()]
}
}