├── .gitignore ├── screenshots ├── inline.png └── structured.png ├── Cargo.toml ├── src ├── definition.rs ├── lib.rs ├── field │ ├── garbage.rs │ ├── padding.rs │ ├── unknown.rs │ ├── constant.rs │ ├── mod.rs │ └── named.rs └── formatter │ ├── mod.rs │ ├── inline.rs │ └── structured.rs └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /screenshots/inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meh/rorschach/HEAD/screenshots/inline.png -------------------------------------------------------------------------------- /screenshots/structured.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/meh/rorschach/HEAD/screenshots/structured.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate byteorder; 2 | extern crate ansi_term; 3 | 4 | /// Endianness for fields. 5 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 6 | pub enum Endian { 7 | Little, 8 | Big, 9 | } 10 | 11 | impl Default for Endian { 12 | #[cfg(target_endian = "big")] 13 | fn default() -> Self { 14 | Endian::Big 15 | } 16 | 17 | #[cfg(target_endian = "little")] 18 | fn default() -> Self { 19 | Endian::Little 20 | } 21 | } 22 | 23 | pub use Endian::{Little as LittleEndian, Big as BigEndian}; 24 | 25 | mod definition; 26 | pub use definition::Definition; 27 | 28 | pub mod field; 29 | pub use field::Field; 30 | 31 | pub mod formatter; 32 | pub use formatter::Formatter; 33 | -------------------------------------------------------------------------------- /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/padding.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | use {Field}; 3 | 4 | #[derive(Clone, PartialEq, Eq, Debug)] 5 | pub struct Padding { 6 | bits: usize, 7 | } 8 | 9 | impl Padding { 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::Padding(Padding { 41 | bits: self.bits.expect("missing field size"), 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/formatter/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read, Write}; 2 | use ansi_term; 3 | use {Definition}; 4 | 5 | /// Trait for formatters. 6 | pub trait Formatter { 7 | fn format(&self, def: &Definition, input: R, output: W) -> io::Result<()>; 8 | } 9 | 10 | /// Color for styling. 11 | pub use ansi_term::Colour as Color; 12 | 13 | /// Styles for fields that do not have instance styling. 14 | #[derive(Clone, Copy, PartialEq, Debug)] 15 | pub struct Style { 16 | pub default: ansi_term::Style, 17 | pub constant: (ansi_term::Style, ansi_term::Style), 18 | pub padding: (ansi_term::Style, ansi_term::Style), 19 | pub garbage: ansi_term::Style, 20 | pub unknown: ansi_term::Style, 21 | } 22 | 23 | impl Default for Style { 24 | fn default() -> Self { 25 | Style { 26 | default: Color::White.normal(), 27 | constant: (Color::Fixed(237).normal(), Color::Fixed(255).on(Color::Red)), 28 | padding: (Color::Fixed(237).normal(), Color::Fixed(255).on(Color::Red)), 29 | garbage: Color::Fixed(237).normal(), 30 | unknown: Color::Fixed(240).normal(), 31 | } 32 | } 33 | } 34 | 35 | mod structured; 36 | pub use self::structured::Structured; 37 | 38 | mod inline; 39 | pub use self::inline::Inline; 40 | -------------------------------------------------------------------------------- /src/field/unknown.rs: -------------------------------------------------------------------------------- 1 | use std::io::{self, Read}; 2 | use {Field}; 3 | 4 | /// An unkown field, it means you're not sure whether the data is padding or 5 | /// garbage. 6 | #[derive(Clone, PartialEq, Eq, Debug)] 7 | pub struct Unknown { 8 | bits: usize, 9 | } 10 | 11 | impl Unknown { 12 | /// Get how many bits the field is. 13 | pub fn bits(&self) -> usize { 14 | self.bits 15 | } 16 | 17 | /// Read the data from a `Read` for this field. 18 | pub fn read(&self, mut buffer: R) -> io::Result> { 19 | let mut data = vec![0u8; super::bytes(self.bits)]; 20 | try!(buffer.read(&mut data)); 21 | 22 | Ok(data) 23 | } 24 | } 25 | 26 | /// A builder for an unknown field. 27 | #[derive(Default)] 28 | pub struct Builder { 29 | bits: Option, 30 | } 31 | 32 | impl Builder { 33 | /// Defines the size in bits. 34 | pub fn bits(mut self, value: usize) -> Self { 35 | self.bits = Some(value); 36 | self 37 | } 38 | 39 | /// Defines the size in bytes. 40 | pub fn bytes(self, value: usize) -> Self { 41 | self.bits(value * 8) 42 | } 43 | } 44 | 45 | impl Into for Builder { 46 | fn into(self) -> Field { 47 | Field::Unknown(Unknown { 48 | bits: self.bits.expect("missing field size"), 49 | }) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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