z-base-32-0.1.2/.cargo_vcs_info.json0000644000000001120000000000100124310ustar { "git": { "sha1": "6956858e03c50c65f30d8cf970cf395a3a90dc1c" } } z-base-32-0.1.2/.github/workflows/ci.yml000064400000000000000000000055440072674642500160020ustar 00000000000000name: CI on: release: types: [created] push: schedule: - cron: '00 01 * * *' jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: components: rustfmt, clippy toolchain: stable - name: Lint with rustfmt uses: actions-rs/cargo@v1 with: command: fmt - name: Lint with clippy uses: actions-rs/cargo@v1 with: command: clippy args: --all-targets --all-features - name: Test with cargo uses: actions-rs/cargo@v1.0.1 with: command: test build: runs-on: ${{ matrix.os }} needs: lint strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-latest, macos-latest, windows-latest] steps: - name: Checkout uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: stable - uses: messense/maturin-action@v1 with: maturin-version: latest command: build args: --release rust-publish: if: github.event_name == 'release' && github.event.action == 'created' needs: build runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions-rs/toolchain@v1 with: toolchain: stable - name: Publish on crates.io run: | cargo login ${{ secrets.CRATES_TOKEN }} cargo publish python-publish: needs: build runs-on: ${{ matrix.os }} strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9] os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/checkout@v1 - uses: actions/setup-python@v1 with: python-version: ${{ matrix.python-version }} - name: Install latest nightly uses: actions-rs/toolchain@v1 with: toolchain: stable - uses: messense/maturin-action@v1 with: maturin-version: latest command: build args: --release --interpreter python${{matrix.python_version}} - name: Install wheels if: matrix.os == 'windows-latest' run: pip install --find-links=target\wheels whl - name: Install wheels if: matrix.os != 'windows-latest' run: pip install target/wheels/*.whl - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: target/wheels/*.whl env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: PyPi publish if: github.event_name == 'release' && github.event.action == 'created' env: MATURIN_PASSWORD: ${{ secrets.PYPI_TOKEN }} run: maturin publish --skip-existing -u __token__ z-base-32-0.1.2/.gitignore000064400000000000000000000000230072674642500132420ustar 00000000000000/target Cargo.lock z-base-32-0.1.2/CHANGELOG.md000064400000000000000000000007010072674642500130660ustar 00000000000000# Changelog ## [Unreleased] ## [0.1.2] - 2021-08-25 ## Changed - fix maturin version in `pyproject.toml` ## [0.1.1] - 2021-08-25 ### Added - `pyproject.toml` for building from source ## 0.1.0 - 2021-08-19 ### Added - initial release [Unreleased]: https://github.com/matusf/z-base-32/compare/0.1.2...HEAD [0.1.2]: https://github.com/matusf/z-base-32/compare/0.1.1...0.1.2 [0.1.1]: https://github.com/matusf/z-base-32/compare/0.1.0...0.1.1 z-base-32-0.1.2/Cargo.toml0000644000000021060000000000100104340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies # # If you believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] edition = "2018" name = "z-base-32" version = "0.1.2" authors = ["Matus Ferech "] description = "z-base-32: human-oriented base-32 encoding" readme = "README.md" keywords = ["zbase32", "base32", "encode", "decode", "python"] categories = ["encoding"] license = "MIT" repository = "https://github.com/matusf/z-base-32" [lib] name = "zbase32" crate-type = ["cdylib", "rlib"] [dependencies.pyo3] version = "0.14.2" features = ["extension-module"] optional = true [dev-dependencies.quickcheck] version = "1" [features] python = ["pyo3"] z-base-32-0.1.2/Cargo.toml.orig000064400000000000000000000012460072674642500141510ustar 00000000000000[package] name = "z-base-32" version = "0.1.2" edition = "2018" authors = ["Matus Ferech "] license = "MIT" repository = "https://github.com/matusf/z-base-32" readme = "README.md" description = "z-base-32: human-oriented base-32 encoding" keywords = ["zbase32", "base32", "encode", "decode", "python"] categories = ["encoding"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] name = "zbase32" crate-type = ["cdylib", "rlib"] [features] python = ["pyo3"] [dependencies] [dependencies.pyo3] version = "0.14.2" features = ["extension-module"] optional = true [dev-dependencies] quickcheck = "1" z-base-32-0.1.2/README.md000064400000000000000000000024100072674642500125330ustar 00000000000000# z-base-32 ![ci](https://github.com/matusf/z-base-32/actions/workflows/ci.yml/badge.svg) The `z-base-32` is a human oriented base32 encoding. ## API The library exposes two functions with following signatures and error type: ```rs pub fn encode(input: &[u8]) -> String; pub fn decode(input: &str) -> Result, DecodeError>; pub struct DecodeError; ``` ### Example ```rs use zbase32::{encode, decode}; fn main() { assert_eq!(encode(b"foo"), "c3zs6".to_string()); assert_eq!(Ok(b"foo"), decode("c3zs6".to_string())); assert_eq!(decode(&encode(b"foo")).unwrap(), b"foo") } ``` ## Python ### Building This crate can be compiled with feature flag `python` in which case it produces python bindings. To build a Python wheels use [`maturin`](https://github.com/PyO3/maturin): ```console maturin build --cargo-extra-args="--features python" ``` ### API ```py def encode(input: bytes) -> str: def decode(input: str) -> bytes: class DecodeError(Exception): ``` #### Example ```py import zbase32 assert zbase32.encode(b'foo') == 'c3zs6' assert zbase32.decode('c3zs6') == b'foo' try: zbase32.decode('invalid@char') except zbase32.DecodeError as e: print(e) ``` ## References - z-base-32-0.1.2/pyproject.toml000064400000000000000000000002060072674642500141710ustar 00000000000000[build-system] requires = ["maturin>=0.11.3,<0.12"] build-backend = "maturin" [tool.maturin] cargo-extra-args = "--features python" z-base-32-0.1.2/src/lib.rs000064400000000000000000000072230072674642500131660ustar 00000000000000#[cfg(feature = "python")] mod python; #[cfg(test)] #[macro_use] extern crate quickcheck; use std::fmt; const ALPHABET: &[u8] = b"ybndrfg8ejkmcpqxot1uwisza345h769"; const INVERSE_ALPHABET: [i8; 123] = [ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 18, -1, 25, 26, 27, 30, 29, 7, 31, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 24, 1, 12, 3, 8, 5, 6, 28, 21, 9, 10, -1, 11, 2, 16, 13, 14, 4, 22, 17, 19, -1, 20, 15, 0, 23, ]; #[derive(Debug, PartialEq)] pub struct DecodeError; impl fmt::Display for DecodeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "DecodeError: Non-zbase32 digit found") } } pub fn encode(input: &[u8]) -> String { let mut result = Vec::new(); let chunks = input.chunks(5); for chunk in chunks { let buf = { let mut buf = [0u8; 5]; for (i, &b) in chunk.iter().enumerate() { buf[i] = b; } buf }; result.push(ALPHABET[((buf[0] & 0xF8) >> 3) as usize]); result.push(ALPHABET[((buf[0] & 0x07) << 2 | (buf[1] & 0xC0) >> 6) as usize]); result.push(ALPHABET[((buf[1] & 0x3E) >> 1) as usize]); result.push(ALPHABET[((buf[1] & 0x01) << 4 | (buf[2] & 0xF0) >> 4) as usize]); result.push(ALPHABET[((buf[2] & 0x0F) << 1 | (buf[3] & 0x80) >> 7) as usize]); result.push(ALPHABET[((buf[3] & 0x7C) >> 2) as usize]); result.push(ALPHABET[((buf[3] & 0x03) << 3 | (buf[4] & 0xE0) >> 5) as usize]); result.push(ALPHABET[(buf[4] & 0x1F) as usize]); } let expected_len = (input.len() as f32 * 8.0 / 5.0).ceil() as usize; for _ in 0..(result.len() - expected_len) { result.pop(); } unsafe { String::from_utf8_unchecked(result) } } pub fn decode(input: &str) -> Result, DecodeError> { let mut result = Vec::new(); for chunk in input.as_bytes().chunks(8) { let buf = { let mut buf = [0u8; 8]; for (i, &ch) in chunk.iter().enumerate() { match INVERSE_ALPHABET.get(ch as usize) { Some(-1) => return Err(DecodeError), Some(x) => buf[i] = *x as u8, None => return Err(DecodeError), }; } buf }; result.push((buf[0] << 3) | (buf[1] >> 2)); result.push((buf[1] << 6) | (buf[2] << 1) | (buf[3] >> 4)); result.push((buf[3] << 4) | (buf[4] >> 1)); result.push((buf[4] << 7) | (buf[5] << 2) | (buf[6] >> 3)); result.push((buf[6] << 5) | buf[7]); } for _ in 0..(result.len() - input.len() * 5 / 8) { result.pop(); } Ok(result) } #[cfg(test)] mod tests { use super::*; #[test] fn simple_encode() { assert_eq!(encode(b"asdasd"), "cf3seamuco".to_string()); } #[test] fn simple_decode() { assert_eq!(decode("cf3seamu"), Ok(b"asdas".to_vec())) } #[test] fn encode_decode() { assert_eq!(decode(&encode(b"foo")).unwrap(), b"foo") } #[test] fn invalid_decode() { assert_eq!(decode("bar#"), Err(DecodeError)) } quickcheck! { fn prop(input: Vec) -> bool { decode(&encode(&input)).unwrap() == input } } quickcheck! { #[allow(unused_must_use)] fn not_panic(input: String) -> bool { decode(&input); true } } } z-base-32-0.1.2/src/python.rs000064400000000000000000000016330072674642500137400ustar 00000000000000use pyo3::{create_exception, exceptions::PyException, prelude::*, types::PyBytes}; create_exception!(zbase32, DecodeError, PyException); #[inline] #[pyfunction] #[pyo3(text_signature = "(input: bytes) -> str")] /// Decode zbase32 encoded string to bytes fn decode<'a>(py: Python<'a>, input: &'a str) -> PyResult<&'a PyBytes> { match crate::decode(input) { Ok(b) => Ok(PyBytes::new(py, &b)), Err(_) => Err(DecodeError::new_err("Non-zbase32 digit found")), } } #[inline] #[pyfunction] #[pyo3(text_signature = "(input: str) -> bytes")] /// Encode bytes using a zbase32 and return encoded string fn encode(input: &[u8]) -> String { crate::encode(input) } #[pymodule] fn zbase32(py: Python, m: &PyModule) -> PyResult<()> { m.add("DecodeError", py.get_type::())?; m.add_function(wrap_pyfunction!(decode, m)?)?; m.add_function(wrap_pyfunction!(encode, m)?)?; Ok(()) }