Skip to content

Commit

Permalink
Add encode_dag_cbor (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarshalX authored Feb 26, 2024
1 parent 7e36301 commit 467caa3
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 9 deletions.
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "libipld"
version = "1.1.1"
version = "1.2.0"
edition = "2021"
license = "MIT"
description = "Python binding to the Rust IPLD library"
Expand All @@ -20,6 +20,7 @@ futures = "0.3"
libipld = { version = "0.16.0", features = ["dag-cbor"] }
iroh-car = "0.4.0"
multibase = "0.9"
byteorder = "1.5.0"

[workspace]
members = [ "profiling" ]
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import libipld
print(libipld.decode_cid('bafyreig7jbijxpn4lfhvnvyuwf5u5jyhd7begxwyiqe7ingwxycjdqjjoa'))
# Output: {'hash': {'size': 32, 'code': 18, 'digest': b'\xdfHP\x9b\xbd\xbcYOV\xd7\x14\xb1{N\xa7\x07\x1f\xc2C^\xd8D\t\xf44\xd6\xbe\x04\x91\xc1)p'}, 'version': 1, 'codec': 113}

# Decode a DAG CBOR
print(libipld.decode_dag_cbor(b'\xa2aa\x0cabfhello!\x82\x00\x01'))
# DAG CBOR
print(libipld.decode_dag_cbor(b'\xa2aa\x0cabfhello!'))
# Output: {'a': 12, 'b': 'hello!'}
print(libipld.encode_dag_cbor({'a': 12, 'b': 'hello!'}))
# Output: b'\xa2aa\x0cabfhello!'

# multibase
print(libipld.decode_multibase('ueWVzIG1hbmkgIQ'))
Expand All @@ -24,20 +26,25 @@ print(libipld.encode_multibase('u', b'yes mani !'))

### Features

- Decode DAG CBOR (`decode_cid(str) -> dict`)
- Decode CID (`decode_dag_cbor(bytes) -> dict`, `decode_dag_cbor_multi(bytes) -> list[dict]`)
- Decode DAG CBOR (`decode_dag_cbor(bytes) -> dict`, `decode_dag_cbor_multi(bytes) -> list[dict]`)
- Encode DAG CBOR (`encode_dag_cbor(obj) -> bytes`)
- Decode CID (`decode_cid(str) -> dict`)
- Decode CAR (`decode_car(bytes) -> tuple[dict, dict[str, dict]]`). Returns a header and blocks mapped by CID.
- Decode Multibase (`decode_multibase(str) -> tuple[str, bytes]`). Returns base and data.
- Encode Multibase (`encode_multibase(str, bytes) -> str`). Accepts base and data.

Note: stub file will be provided in the future.

## Requirements

- Python 3.7 or higher.

## Installing

You can install or upgrade `libipld` via

```bash
pip install -U libipld
pip install libipld
```

### Contributing
Expand Down
82 changes: 79 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use std::io::{BufReader, Cursor, Read, Seek};
use std::io::{BufReader, BufWriter, Cursor, Read, Seek, Write};

use ::libipld::cbor::{cbor, cbor::MajorKind, decode};
use ::libipld::cbor::error::LengthOutOfRange;
use ::libipld::cbor::{cbor, cbor::MajorKind, decode, encode};
use ::libipld::cbor::error::{LengthOutOfRange, NumberOutOfRange, UnknownTag};
use ::libipld::cid::Cid;
use anyhow::Result;
use byteorder::{BigEndian, ByteOrder};
use futures::{executor, stream::StreamExt};
use iroh_car::{CarHeader, CarReader, Error};
use pyo3::{PyObject, Python};
Expand Down Expand Up @@ -105,6 +106,68 @@ fn decode_dag_cbor_to_pyobject<R: Read + Seek>(py: Python, r: &mut R) -> Result<
Ok(py_object)
}

fn encode_dag_cbor_from_pyobject<W: Write>(py: Python, obj: &PyAny, w: &mut W) -> Result<()> {
if obj.is_none() {
encode::write_null(w)
} else if let Ok(b) = obj.extract::<bool>() {
let buf = if b { [cbor::TRUE.into()] } else { [cbor::FALSE.into()] };
Ok(w.write_all(&buf)?)
} else if let Ok(i) = obj.extract::<i128>() {
if i < 0 {
if -(i + 1) > u64::MAX as i128 {
return Err(NumberOutOfRange::new::<i128>().into());
}
encode::write_u64(w, MajorKind::NegativeInt, -(i + 1) as u64)
} else {
if i > u64::MAX as i128 {
return Err(NumberOutOfRange::new::<i128>().into());
}
encode::write_u64(w, MajorKind::UnsignedInt, i as u64)
}
} else if let Ok(f) = obj.extract::<f64>() {
if !f.is_finite() {
return Err(NumberOutOfRange::new::<f64>().into());
}
let mut buf = [0xfb, 0, 0, 0, 0, 0, 0, 0, 0];
BigEndian::write_f64(&mut buf[1..], f);
Ok(w.write_all(&buf)?)
} else if let Ok(b) = obj.extract::<&[u8]>() {
encode::write_u64(w, MajorKind::ByteString, b.len() as u64)?;
Ok(w.write_all(b)?)
} else if let Ok(s) = obj.extract::<String>() {
// FIXME (MarshalX): it's not efficient to try to parse it as CID
let cid = Cid::try_from(s.as_str());
if let Ok(cid) = cid {
encode::write_tag(w, 42)?;
// FIXME (MarshalX): allocates
let buf = cid.to_bytes();
let len = buf.len();
encode::write_u64(w, MajorKind::ByteString, len as u64 + 1)?;
w.write_all(&[0])?;
Ok(w.write_all(&buf[..len])?)
} else {
encode::write_u64(w, MajorKind::TextString, s.len() as u64)?;
Ok(w.write_all(s.as_bytes())?)
}
} else if let Ok(l) = obj.downcast::<PyList>() {
encode::write_u64(w, MajorKind::Array, l.len() as u64)?;
for item in l.iter() {
encode_dag_cbor_from_pyobject(py, item, w)?;
}
Ok(())
} else if let Ok(d) = obj.downcast::<PyDict>() {
encode::write_u64(w, MajorKind::Map, d.len() as u64)?;
// FIXME (MarshalX): care about the order of keys?
for (key, value) in d.iter() {
encode_dag_cbor_from_pyobject(py, key, w)?;
encode_dag_cbor_from_pyobject(py, value, w)?;
}
Ok(())
} else {
return Err(UnknownTag(0).into());
}
}

#[pyfunction]
fn decode_dag_cbor_multi<'py>(py: Python<'py>, data: &[u8]) -> PyResult<&'py PyList> {
let mut reader = BufReader::new(Cursor::new(data));
Expand Down Expand Up @@ -158,6 +221,18 @@ fn decode_dag_cbor(py: Python, data: &[u8]) -> PyResult<PyObject> {
}
}

#[pyfunction]
fn encode_dag_cbor<'py>(py: Python<'py>, data: &PyAny) -> PyResult<&'py PyBytes> {
let mut buf = &mut BufWriter::new(Vec::new());
if let Err(e) = encode_dag_cbor_from_pyobject(py, data, &mut buf) {
return Err(get_err("Failed to encode DAG-CBOR", e.to_string()));
}
if let Err(e) = buf.flush() {
return Err(get_err("Failed to flush buffer", e.to_string()));
}
Ok(PyBytes::new(py, &buf.get_ref()))
}

#[pyfunction]
fn decode_cid(py: Python, data: String) -> PyResult<&PyDict> {
let cid = Cid::try_from(data.as_str());
Expand Down Expand Up @@ -197,6 +272,7 @@ fn libipld(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(decode_cid, m)?)?;
m.add_function(wrap_pyfunction!(decode_car, m)?)?;
m.add_function(wrap_pyfunction!(decode_dag_cbor, m)?)?;
m.add_function(wrap_pyfunction!(encode_dag_cbor, m)?)?;
m.add_function(wrap_pyfunction!(decode_dag_cbor_multi, m)?)?;
m.add_function(wrap_pyfunction!(decode_multibase, m)?)?;
m.add_function(wrap_pyfunction!(encode_multibase, m)?)?;
Expand Down

0 comments on commit 467caa3

Please sign in to comment.