initial commit, bencode decoding
This commit is contained in:
143
src/bencode/decode.rs
Normal file
143
src/bencode/decode.rs
Normal file
@@ -0,0 +1,143 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::num::ParseIntError;
|
||||
use std::str::{self, Utf8Error};
|
||||
|
||||
use bencode::Object;
|
||||
use buffer::Buffer;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DecodeError;
|
||||
|
||||
impl From<Utf8Error> for DecodeError {
|
||||
fn from(_: Utf8Error) -> Self {
|
||||
DecodeError
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for DecodeError {
|
||||
fn from(_: ParseIntError) -> Self {
|
||||
DecodeError
|
||||
}
|
||||
}
|
||||
|
||||
pub type DecodeResult<T> = Result<T, DecodeError>;
|
||||
|
||||
pub fn decode(data: &[u8]) -> DecodeResult<Object> {
|
||||
let mut buf = Buffer::new(data);
|
||||
decode_object(&mut buf)
|
||||
}
|
||||
|
||||
fn decode_object(buf: &mut Buffer) -> DecodeResult<Object> {
|
||||
match buf.get(0) {
|
||||
Some(b'i') => {
|
||||
buf.advance(1);
|
||||
decode_int(buf, b'e')
|
||||
},
|
||||
Some(b'0' ... b'9') => {
|
||||
decode_bytes(buf)
|
||||
}
|
||||
Some(b'l') => {
|
||||
buf.advance(1);
|
||||
let mut list = Vec::new();
|
||||
while let Some(term) = buf.get(0) {
|
||||
if term == b'e' {
|
||||
buf.advance(1);
|
||||
break;
|
||||
}
|
||||
list.push(decode_object(buf)?);
|
||||
}
|
||||
Ok(Object::List(list))
|
||||
}
|
||||
Some(b'd') => {
|
||||
buf.advance(1);
|
||||
let mut dict = BTreeMap::new();
|
||||
while let Some(term) = buf.get(0) {
|
||||
if term == b'e' {
|
||||
buf.advance(1);
|
||||
break;
|
||||
}
|
||||
let key = _decode_bytes(buf)?;
|
||||
let val = decode_object(buf)?;
|
||||
dict.insert(key, val);
|
||||
}
|
||||
Ok(Object::Dict(dict))
|
||||
}
|
||||
_ => Err(DecodeError),
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_int(buf: &mut Buffer, term: u8) -> DecodeResult<Object> {
|
||||
_decode_int(buf, term).map(|num| Object::Int(num))
|
||||
}
|
||||
|
||||
fn _decode_int(buf: &mut Buffer, term: u8) -> DecodeResult<i64> {
|
||||
if let Some(end) = buf.find(term) {
|
||||
let obj = {
|
||||
let num = str::from_utf8(&buf[..end])?;
|
||||
num.parse()?
|
||||
};
|
||||
buf.advance(end + 1);
|
||||
Ok(obj)
|
||||
} else {
|
||||
Err(DecodeError)
|
||||
}
|
||||
}
|
||||
|
||||
fn decode_bytes(buf: &mut Buffer) -> DecodeResult<Object> {
|
||||
_decode_bytes(buf).map(|bytes| Object::Bytes(bytes))
|
||||
}
|
||||
|
||||
fn _decode_bytes(buf: &mut Buffer) -> Result<Vec<u8>, DecodeError> {
|
||||
let size = _decode_int(buf, b':')? as usize;
|
||||
let bytes = buf[..size].to_vec();
|
||||
buf.advance(size);
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int_pos() {
|
||||
let mut buf = Buffer::new(b"i1337e");
|
||||
buf.advance(1);
|
||||
|
||||
assert_eq!(decode_int(&mut buf, b'e').unwrap(), Object::Int(1337));
|
||||
assert_eq!(buf.pos(), 6);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_int_neg() {
|
||||
let mut buf = Buffer::new(b"i-1337e");
|
||||
buf.advance(1);
|
||||
|
||||
assert_eq!(decode_int(&mut buf, b'e').unwrap(), Object::Int(-1337));
|
||||
assert_eq!(buf.pos(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bytes() {
|
||||
let mut buf = Buffer::new(b"5:hello");
|
||||
|
||||
assert_eq!(decode_bytes(&mut buf).unwrap(), Object::Bytes(b"hello".to_vec()));
|
||||
assert_eq!(buf.pos(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list() {
|
||||
let mut buf = Buffer::new(b"li1ei2ei3ee");
|
||||
|
||||
let obj = decode_object(&mut buf).unwrap();
|
||||
let list = obj.as_list().unwrap();
|
||||
|
||||
assert_eq!(list, vec![Object::Int(1), Object::Int(2), Object::Int(3)]);
|
||||
assert_eq!(buf.pos(), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dict() {
|
||||
let mut buf = Buffer::new(b"d5:helloi1337ee");
|
||||
|
||||
let obj = decode_object(&mut buf).unwrap();
|
||||
let dict = obj.as_dict().unwrap();
|
||||
|
||||
assert_eq!(dict[&b"hello"[..]], Object::Int(1337));
|
||||
assert_eq!(buf.pos(), 15);
|
||||
}
|
||||
72
src/bencode/mod.rs
Normal file
72
src/bencode/mod.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
mod decode;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
use std::str;
|
||||
|
||||
pub use self::decode::{decode, DecodeError, DecodeResult};
|
||||
|
||||
#[derive(Eq, PartialEq)]
|
||||
pub enum Object {
|
||||
Int(i64),
|
||||
Bytes(Vec<u8>),
|
||||
List(Vec<Object>),
|
||||
Dict(BTreeMap<Vec<u8>, Object>),
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub fn as_int(self) -> Option<i64> {
|
||||
match self {
|
||||
Object::Int(num) => Some(num),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bytes(self) -> Option<Vec<u8>> {
|
||||
match self {
|
||||
Object::Bytes(bytes) => Some(bytes),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_list(self) -> Option<Vec<Object>> {
|
||||
match self {
|
||||
Object::List(list) => Some(list),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_dict(self) -> Option<BTreeMap<Vec<u8>, Object>> {
|
||||
match self {
|
||||
Object::Dict(dict) => Some(dict),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Object {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Object::Int(num) => write!(f, "{}", num),
|
||||
Object::Bytes(ref bytes) => {
|
||||
match str::from_utf8(bytes) {
|
||||
Ok(s) => write!(f, "{}", s),
|
||||
Err(_) => {
|
||||
for &b in bytes.iter() {
|
||||
write!(f, "{:X}", b)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
Object::List(ref list) => {
|
||||
f.debug_list().entries(list.iter()).finish()
|
||||
}
|
||||
Object::Dict(ref dict) => {
|
||||
f.debug_map()
|
||||
.entries(dict.iter().map(|(k, v)| (Object::Bytes(k.clone()), v)))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
96
src/buffer.rs
Normal file
96
src/buffer.rs
Normal file
@@ -0,0 +1,96 @@
|
||||
use std::ops::{Index, Range, RangeFrom, RangeFull, RangeTo};
|
||||
|
||||
pub struct Buffer<'b> {
|
||||
inner: &'b [u8],
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'b> Buffer<'b> {
|
||||
pub fn new(inner: &[u8]) -> Buffer {
|
||||
Buffer {
|
||||
inner: inner,
|
||||
pos: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance(&mut self, amt: usize) {
|
||||
self.pos += amt
|
||||
}
|
||||
|
||||
pub fn find(&self, byte: u8) -> Option<usize> {
|
||||
self[..].iter().position(|&b| b == byte)
|
||||
}
|
||||
|
||||
pub fn get(&self, idx: usize) -> Option<u8> {
|
||||
self[..].get(idx).map(|&b| b)
|
||||
}
|
||||
|
||||
pub fn pos(&self) -> usize {
|
||||
self.pos
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Index<Range<usize>> for Buffer<'b> {
|
||||
type Output = [u8];
|
||||
|
||||
fn index(&self, r: Range<usize>) -> &[u8] {
|
||||
&self.inner[self.pos + r.start..self.pos + r.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Index<RangeFrom<usize>> for Buffer<'b> {
|
||||
type Output = [u8];
|
||||
|
||||
fn index(&self, r: RangeFrom<usize>) -> &[u8] {
|
||||
&self.inner[self.pos + r.start..]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Index<RangeTo<usize>> for Buffer<'b> {
|
||||
type Output = [u8];
|
||||
|
||||
fn index(&self, r: RangeTo<usize>) -> &[u8] {
|
||||
&self.inner[self.pos..self.pos + r.end]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> Index<RangeFull> for Buffer<'b> {
|
||||
type Output = [u8];
|
||||
|
||||
fn index(&self, _: RangeFull) -> &[u8] {
|
||||
&self.inner[self.pos..]
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_advance() {
|
||||
let mut b = Buffer::new(b"hello");
|
||||
b.advance(2);
|
||||
assert_eq!(b.pos(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find() {
|
||||
let b = Buffer::new(b"hello");
|
||||
assert_eq!(b.find(b'l'), Some(2));
|
||||
assert_eq!(b.find(b'a'), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let mut b = Buffer::new(b"hello");
|
||||
b.advance(2);
|
||||
assert_eq!(b.get(0), Some(b'l'));
|
||||
assert_eq!(b.get(1), Some(b'l'));
|
||||
assert_eq!(b.get(2), Some(b'o'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range() {
|
||||
let mut b = Buffer::new(b"hello");
|
||||
b.advance(2);
|
||||
assert_eq!(b[1..2], b"l"[..]);
|
||||
assert_eq!(b[1..], b"lo"[..]);
|
||||
assert_eq!(b[..2], b"ll"[..]);
|
||||
assert_eq!(b[..], b"llo"[..]);
|
||||
}
|
||||
2
src/lib.rs
Normal file
2
src/lib.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
pub mod bencode;
|
||||
pub mod buffer;
|
||||
Reference in New Issue
Block a user