├── .gitignore ├── Cargo.toml ├── README.md ├── screenshots ├── inline.png └── structured.png └── src ├── definition.rs ├── field ├── constant.rs ├── garbage.rs ├── mod.rs ├── named.rs ├── padding.rs └── unknown.rs ├── formatter ├── inline.rs ├── mod.rs └── structured.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rorschach" 3 | version = "0.1.3" 4 | 5 | authors = ["meh. "] 6 | license = "WTFPL" 7 | exclude = ["screenshots/*"] 8 | 9 | description = "Binary data definition and formatter." 10 | repository = "https://github.com/meh/rorschach" 11 | keywords = ["formatter", "binary", "hex"] 12 | 13 | [dependencies] 14 | byteorder = "0.5" 15 | ansi_term = "0.8" 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Rorschach 2 | ========= 3 | Pretty print binary blobs based on common layout definition. 4 | 5 | Example 6 | ------- 7 | ```rust 8 | use rorschach::{Definition, Field, LittleEndian}; 9 | use rorschach::formatter::{self, Color}; 10 | 11 | let def = Definition::default() 12 | .field(Field::named("sequence") 13 | .is::(LittleEndian) 14 | .style(Color::Fixed(255).normal())) 15 | .field(Field::named("buttons") 16 | .bytes(3) 17 | .binary() 18 | .style(Color::Fixed(3).normal())) 19 | .field(Field::named("trigger.left") 20 | .is::(LittleEndian) 21 | .style(Color::Fixed(255).on(Color::Fixed(63)).underline())) 22 | .field(Field::named("trigger.right") 23 | .is::(LittleEndian) 24 | .style(Color::Fixed(255).on(Color::Fixed(63)))) 25 | .field(Field::padding() 26 | .bytes(3)) 27 | .field(Field::named("pad.left.x") 28 | .is::(LittleEndian) 29 | .style(Color::Fixed(255).on(Color::Fixed(27)).underline())) 30 | .field(Field::named("pad.left.y") 31 | .is::(LittleEndian) 32 | .style(Color::Fixed(27).normal())) 33 | .field(Field::named("pad.right.x") 34 | .is::(LittleEndian) 35 | .style(Color::Fixed(255).on(Color::Fixed(36)).underline())) 36 | .field(Field::named("pad.right.y") 37 | .is::(LittleEndian) 38 | .style(Color::Fixed(36).normal())) 39 | .field(Field::padding() 40 | .bytes(12)) 41 | .field(Field::named("acceleration.pitch") 42 | .is::(LittleEndian) 43 | .style(Color::Fixed(124).normal())) 44 | .field(Field::named("acceleration.yaw") 45 | .is::(LittleEndian) 46 | .style(Color::Fixed(160).normal())) 47 | .field(Field::named("acceleration.roll") 48 | .is::(LittleEndian) 49 | .style(Color::Fixed(196).normal())) 50 | .field(Field::named("orientation.pitch") 51 | .is::(LittleEndian) 52 | .style(Color::Fixed(57).normal())) 53 | .field(Field::named("orientation.yaw") 54 | .is::(LittleEndian) 55 | .style(Color::Fixed(93).normal())) 56 | .field(Field::named("orientation.roll") 57 | .is::(LittleEndian) 58 | .style(Color::Fixed(129).normal())) 59 | .field(Field::padding() 60 | .bytes(16)); 61 | ``` 62 | 63 | Structured 64 | ---------- 65 | The structured formatter takes inspiration from the ASCII art tables often used 66 | in network related RFCs. 67 | 68 | ```rust 69 | formatter::Structured::default() 70 | .header(true) 71 | .style(Default::default()) 72 | .format(&def, buffer, io::stdout()) 73 | .unwrap() 74 | ``` 75 | 76 | ![Structured Screenshot](/screenshots/structured.png?raw=true) 77 | 78 | Inline 79 | ------ 80 | The inline formatter is the simplest formatter, it just prints the bytes as 81 | hexadecimal one after another, but it does support coloring which can help 82 | reversing formats. 83 | 84 | ```rust 85 | formatter::Inline::default() 86 | .newline(true) 87 | .split(4) 88 | .style(Default::default()) 89 | .format(&def, buffer, io::stdout()) 90 | .unwrap() 91 | ``` 92 | 93 | ![Inline Screenshot](/screenshots/inline.png?raw=true) 94 | -------------------------------------------------------------------------------- /screenshots/inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meh/rorschach/28535ca49cf80bf5f2c1d8d9ba9711175e2360f0/screenshots/inline.png -------------------------------------------------------------------------------- /screenshots/structured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meh/rorschach/28535ca49cf80bf5f2c1d8d9ba9711175e2360f0/screenshots/structured.png -------------------------------------------------------------------------------- /src/definition.rs: -------------------------------------------------------------------------------- 1 | use {Field}; 2 | 3 | /// A packet definition. 4 | #[derive(Clone, PartialEq, Default, Debug)] 5 | pub struct Definition { 6 | fields: Vec, 7 | } 8 | 9 | impl Definition { 10 | /// The fields in the definition. 11 | pub fn fields(&self) -> &[Field] { 12 | self.fields.as_ref() 13 | } 14 | 15 | /// Add a field. 16 | pub fn field>(mut self, field: T) -> Self { 17 | self.fields.push(field.into()); 18 | self 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/field/constant.rs: -------------------------------------------------------------------------------- 1 | use std::slice; 2 | use std::io::{self, Read}; 3 | use {Field}; 4 | 5 | #[derive(Clone, PartialEq, Eq, Debug)] 6 | pub struct Constant { 7 | bits: usize, 8 | 9 | value: Vec, 10 | } 11 | 12 | impl Constant { 13 | pub fn bits(&self) -> usize { 14 | self.bits 15 | } 16 | 17 | pub fn value(&self) -> &[u8] { 18 | &self.value 19 | } 20 | 21 | pub fn read(&self, mut buffer: R) -> io::Result> { 22 | let mut data = vec![0u8; super::bytes(self.bits)]; 23 | try!(buffer.read(&mut data)); 24 | 25 | Ok(data) 26 | } 27 | } 28 | 29 | #[derive(Default)] 30 | pub struct Builder { 31 | bits: Option, 32 | 33 | value: Option>, 34 | } 35 | 36 | impl Builder { 37 | pub fn bits(mut self, value: usize) -> Self { 38 | self.bits = Some(value); 39 | self 40 | } 41 | 42 | pub fn bytes(self, value: usize) -> Self { 43 | self.bits(value * 8) 44 | } 45 | 46 | pub fn value(mut self, value: T) -> Self { 47 | self.value = Some(unsafe { 48 | slice::from_raw_parts(&value as *const _ as *const u8, 49 | super::bytes(self.bits.unwrap_or(0))) 50 | }.to_vec()); 51 | 52 | self 53 | } 54 | } 55 | 56 | impl Into for Builder { 57 | fn into(self) -> Field { 58 | Field::Constant(Constant { 59 | bits: self.bits.expect("missing field size"), 60 | value: self.value.expect("missing field value"), 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/field/garbage.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | use {Field}; 3 | 4 | #[derive(Clone, PartialEq, Eq, Debug)] 5 | pub struct Garbage { 6 | bits: usize, 7 | } 8 | 9 | impl Garbage { 10 | pub fn bits(&self) -> usize { 11 | self.bits 12 | } 13 | 14 | pub fn read(&self, mut buffer: R) -> io::Result> { 15 | let mut data = vec![0u8; super::bytes(self.bits)]; 16 | try!(buffer.read(&mut data)); 17 | 18 | Ok(data) 19 | } 20 | } 21 | 22 | #[derive(Default)] 23 | pub struct Builder { 24 | bits: Option, 25 | } 26 | 27 | impl Builder { 28 | pub fn bits(mut self, value: usize) -> Self { 29 | self.bits = Some(value); 30 | self 31 | } 32 | 33 | pub fn bytes(self, value: usize) -> Self { 34 | self.bits(value * 8) 35 | } 36 | } 37 | 38 | impl Into for Builder { 39 | fn into(self) -> Field { 40 | Field::Garbage(Garbage { 41 | bits: self.bits.expect("missing field size"), 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/field/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod constant; 2 | pub use self::constant::Constant; 3 | 4 | pub mod garbage; 5 | pub use self::garbage::Garbage; 6 | 7 | pub mod unknown; 8 | pub use self::unknown::Unknown; 9 | 10 | pub mod padding; 11 | pub use self::padding::Padding; 12 | 13 | pub mod named; 14 | pub use self::named::Named; 15 | 16 | /// Field types. 17 | #[derive(Clone, PartialEq, Debug)] 18 | pub enum Field { 19 | Constant(Constant), 20 | Garbage(Garbage), 21 | Unknown(Unknown), 22 | Padding(Padding), 23 | Named(Named), 24 | } 25 | 26 | impl Field { 27 | /// Get how many bits the field is. 28 | pub fn bits(&self) -> usize { 29 | match self { 30 | &Field::Constant(ref v) => v.bits(), 31 | &Field::Garbage(ref v) => v.bits(), 32 | &Field::Unknown(ref v) => v.bits(), 33 | &Field::Padding(ref v) => v.bits(), 34 | &Field::Named(ref v) => v.bits(), 35 | } 36 | } 37 | 38 | /// Create a `Constant` field. 39 | pub fn constant() -> constant::Builder { 40 | constant::Builder::default() 41 | } 42 | 43 | /// Create a `Named` field. 44 | pub fn named>(name: T) -> named::Builder { 45 | named::Builder::default().name(name) 46 | } 47 | 48 | /// Create a `Padding` field. 49 | pub fn padding() -> padding::Builder { 50 | padding::Builder::default() 51 | } 52 | 53 | /// Create a `Garbage` field. 54 | pub fn garbage() -> garbage::Builder { 55 | garbage::Builder::default() 56 | } 57 | 58 | /// Create an `Unknown` field. 59 | pub fn unknown() -> unknown::Builder { 60 | unknown::Builder::default() 61 | } 62 | } 63 | 64 | #[doc(hidden)] 65 | pub fn bytes(bits: usize) -> usize { 66 | (bits as f32 / 8.0).ceil() as usize 67 | } 68 | -------------------------------------------------------------------------------- /src/field/named.rs: -------------------------------------------------------------------------------- 1 | use std::{ptr, mem}; 2 | use std::io::{self, Read}; 3 | use std::any::{Any, TypeId}; 4 | use ansi_term::Style; 5 | use {Field, Endian}; 6 | 7 | #[derive(Clone, PartialEq, Debug)] 8 | pub struct Named { 9 | bits: usize, 10 | 11 | name: String, 12 | style: Option