more documentation, some opcodes

This commit is contained in:
Alain Zscheile 2022-09-23 06:50:42 +02:00
parent d09d68cc30
commit 7c992a5c84
5 changed files with 209 additions and 22 deletions

View file

@ -1,3 +1,5 @@
use crate::intern::Sealed;
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
pub enum ValueType {
@ -6,3 +8,16 @@ pub enum ValueType {
Int_ = 0x49, /* I */
Pointer = 0x50, /* P */
}
#[repr(u16)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, int_enum::IntEnum)]
pub enum OpType {
Call = 0x636c, /* cl */
Jump = 0x6a70, /* jp */
Return = 0x7274, /* rt */
Push = 0x7073, /* ps */
Pop = 0x7071, /* pq */
}
impl Sealed for ValueType {}
impl Sealed for OpType {}

View file

@ -0,0 +1,104 @@
use crate::{consts::OpType, Value};
use core::fmt;
pub enum Instr<'a> {
Call(Value<'a>),
Jump(u32),
Return,
Push(Value<'a>),
Pop,
}
impl Instr<'_> {
#[inline]
pub fn typ(&self) -> OpType {
match self {
Instr::Call(_) => OpType::Call,
Instr::Jump(_) => OpType::Jump,
Instr::Return => OpType::Return,
Instr::Push(_) => OpType::Push,
Instr::Pop => OpType::Pop,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ParseError;
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unable to parse instruction from buffer")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseError {}
impl From<()> for ParseError {
fn from(_: ()) -> Self {
Self
}
}
impl From<crate::ValueParseError> for ParseError {
fn from(_: crate::ValueParseError) -> Self {
Self
}
}
impl<'a> crate::Parse<'a> for Instr<'a> {
type Err = ParseError;
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
if inp.len() < 2 {
return Err(ParseError);
}
let (inp, otyp) = OpType::parse(inp)?;
Ok(match otyp {
OpType::Call => {
let (inp, val) = Value::parse(inp)?;
(inp, Instr::Call(val))
}
OpType::Jump => {
let (inp, val) = u32::parse(inp)?;
(inp, Instr::Jump(val))
}
OpType::Push => {
let (inp, val) = Value::parse(inp)?;
(inp, Instr::Push(val))
}
OpType::Return => (inp, Instr::Return),
OpType::Pop => (inp, Instr::Pop),
})
}
}
#[cfg(any(test, feature = "std"))]
impl Instr<'_> {
pub fn write_to<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
use int_enum::IntEnum;
writer.write_all(&self.typ().int_value().to_be_bytes())?;
match self {
Instr::Call(val) => val.write_to(writer)?,
Instr::Jump(val) => writer.write_all(&val.to_be_bytes())?,
Instr::Push(val) => val.write_to(writer)?,
Instr::Return | Instr::Pop => {}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
proptest::proptest! {
#[test]
fn doesnt_crash(inp in proptest::collection::vec(0..=u8::MAX, 0..256)) {
if let Ok((_, v)) = <Instr as crate::Parse<'_>>::parse(&inp[..]) {
let mut buf = alloc::vec::Vec::with_capacity(inp.len());
v.write_to(&mut buf).unwrap();
}
}
}
}

View file

@ -5,6 +5,10 @@
#[cfg(test)]
extern crate alloc;
mod intern {
pub trait Sealed {}
}
/// parse an object from a byte stream
pub trait Parse<'a>: Sized {
type Err: core::fmt::Debug + Send + Sync;
@ -12,7 +16,65 @@ pub trait Parse<'a>: Sized {
}
pub mod consts;
mod instr;
pub use instr::{Instr, ParseError as InstrParseError};
mod pointer;
pub use pointer::*;
mod value;
pub use value::*;
pub use value::{ParseError as ValueParseError, Value};
impl<'a> Parse<'a> for u8 {
type Err = ();
#[inline]
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
if inp.is_empty() {
Err(())
} else {
Ok((&inp[1..], inp[0]))
}
}
}
impl<'a> Parse<'a> for u16 {
type Err = ();
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
if inp.len() < 2 {
Err(())
} else {
Ok((&inp[2..], Self::from_be_bytes(inp[..2].try_into().unwrap())))
}
}
}
impl<'a> Parse<'a> for u32 {
type Err = ();
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
if inp.len() < 4 {
Err(())
} else {
Ok((&inp[4..], Self::from_be_bytes(inp[..4].try_into().unwrap())))
}
}
}
impl<'a> Parse<'a> for u64 {
type Err = ();
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
if inp.len() < 8 {
Err(())
} else {
Ok((&inp[8..], Self::from_be_bytes(inp[..8].try_into().unwrap())))
}
}
}
impl<'a, T: int_enum::IntEnum + intern::Sealed> Parse<'a> for T
where
T::Int: Parse<'a, Err = ()>,
{
type Err = ();
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), Self::Err> {
let (inp, this) = T::Int::parse(inp)?;
T::from_int(this).map(|this| (inp, this)).map_err(|_| ())
}
}

View file

@ -23,65 +23,64 @@ impl Value<'_> {
}
#[derive(Clone, Copy, Debug)]
pub struct ValueParseError;
pub struct ParseError;
impl fmt::Display for ValueParseError {
impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "unable to parse value from buffer")
}
}
#[cfg(feature = "std")]
impl std::error::Error for ValueParseError {}
impl std::error::Error for ParseError {}
impl<T: int_enum::IntEnum> From<int_enum::IntEnumError<T>> for ValueParseError {
impl From<()> for ParseError {
fn from(_: ()) -> Self {
Self
}
}
impl<T: int_enum::IntEnum> From<int_enum::IntEnumError<T>> for ParseError {
fn from(_: int_enum::IntEnumError<T>) -> Self {
Self
}
}
impl From<core::num::TryFromIntError> for ValueParseError {
impl From<core::num::TryFromIntError> for ParseError {
fn from(_: core::num::TryFromIntError) -> Self {
Self
}
}
impl<'a> crate::Parse<'a> for Value<'a> {
type Err = ValueParseError;
type Err = ParseError;
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ValueParseError> {
fn parse(inp: &'a [u8]) -> Result<(&'a [u8], Self), ParseError> {
if inp.len() < 2 {
return Err(ValueParseError);
return Err(ParseError);
}
let (vtyp, inp) = (ValueType::from_int(inp[0])?, &inp[1..]);
match vtyp {
ValueType::Bytes => {
if inp.len() < 8 {
return Err(ValueParseError);
}
let (len, inp) = inp.split_at(8);
let len: usize = u64::from_be_bytes(len.try_into().unwrap()).try_into()?;
let (inp, len) = u64::parse(inp)?;
let len = usize::try_from(len)?;
if inp.len() < len {
return Err(ValueParseError);
return Err(ParseError);
}
let (data, inp) = inp.split_at(len);
Ok((inp, Value::Bytes(data)))
}
ValueType::Int_ => {
if inp.len() < 8 {
return Err(ValueParseError);
}
let (data, inp) = inp.split_at(8);
let data = u64::from_be_bytes(data.try_into().unwrap());
let (inp, data) = u64::parse(inp)?;
Ok((inp, Value::Int(data)))
}
ValueType::Pointer => {
let (inp, ptr) = Pointer::parse(inp).map_err(|()| ValueParseError)?;
let (inp, ptr) = Pointer::parse(inp)?;
Ok((inp, Value::Pointer(ptr)))
}
ValueType::Atom => {
let (inp, ptr) = Atom::parse(inp).map_err(|()| ValueParseError)?;
let (inp, ptr) = Atom::parse(inp)?;
Ok((inp, Value::Atom(ptr)))
}
}

View file

@ -3,3 +3,10 @@
* use tagged, signed pointers for memory addresses
* use a separate call stack to prevent ROP
* implement message passing
# values
* atoms (u128): atomic values, also used as pointer keys to sign pointers
* bytes ([]u8): arbitrary byte strings
* int (u64): the default integer type
* pointer (u128): signed pointer (can only be accessed with matching key)