| Crates.io | nol36 |
| lib.rs | nol36 |
| version | 0.1.0 |
| created_at | 2025-12-08 19:38:33.163701+00 |
| updated_at | 2025-12-08 19:38:33.163701+00 |
| description | Rust bindings for NOL36 base-36 encoder via WASM runtime |
| homepage | |
| repository | https://github.com/radiiplus/nol36 |
| max_upload_size | |
| id | 1974265 |
| size | 114,609 |
NOL36 is a base-36 encoding protocol designed for robust, portable, and verifiable data representation across constrained and standard computing environments. It combines compact alphanumeric encoding with a chunking architecture, versioning system, and integrity verification to enable reliable data transmission, storage, and inter-process communication in contexts ranging from embedded systems to WebAssembly modules.
NOL36 is architected around four core principles:
std, no_std, and WebAssembly environments without protocol changesLet D₃₆ be the digit set {0,1,...,9,A,B,...,Z} representing values 0 to 35. Each symbol s ∈ D₃₆ encodes log₂(36) ≈ 5.1699 bits of information, yielding an encoding efficiency of 64.0% relative to binary.
The bijective function φ: [0,35] → D₃₆ is defined as:
φ(d) =
d + 48 if 0 ≤ d ≤ 9 (ASCII '0'-'9')
d + 55 if 10 ≤ d ≤ 35 (ASCII 'A'-'Z')
All encoded payloads carry a self-describing header:
Header = { version: u8, type: u8, index: u32 }
Constraints:
version < 64 (6-bit version space)type = 0 (binary payload type)index sequence begins at 0; final chunk sets bit 31 (index | 0x80000000)NOL36 defines three payload structures:
For len(data) ≤ 255:
[PREFIX:8=0x01][VERSION:8][TYPE:8=0x00][LEN:8][DATA:8×LEN]
No checksum; length field provides implicit validation.
[PREFIX:8=0x01][VERSION:8][TYPE:8=0x00][INDEX:32][CHECKSUM:32][DATALEN:32][DATA:8×DATALEN]
CHECKSUM = Σ(data[i]) mod 2³² (additive rolling sum)
[PREFIX:8=0x01][VERSION:8][TYPE:8=0x00][INDEX:32|0x80000000][0:32][0:32]
Signals end of chunk sequence; carries no data.
The function ENCODE36: Byte[] → D₃₆* performs arbitrary-precision base conversion:
B prefixed with 0x01B as big-endian integer N; repeatedly divide N by 36, emitting remaindersφTermination: Conversion stops when quotient reaches zero. If no digits produced, emit '0'.
For input data D where |D| > 255:
D into contiguous blocks C₀, C₁, ..., Cₙ₋₁ where |Cᵢ| = 64 (configurable)Cᵢ → Format B chunk with INDEX = iINDEX = n | 0x80000000'.'When std feature is enabled, encoding of independent chunks utilizes available parallelism:
threads = min(available_parallelism(), chunk_count)
Each thread processes a disjoint subset of chunks; results are reassembled in order.
The function DECODE36: D₃₆* → Byte[] performs inverse conversion:
S over alphabet D₃₆0x01The decoder maintains state Σ = {expv: Option<u8>, rcvd: Vec<(index, Chunk)>}
State Transitions:
expv = None, rcvd = [](index, chunk) into rcvdindex has bit 31 set → Finalizercvd by index; verify contiguous sequence 0..n; concatenate data payloads| Feature | Benefit | Implementation |
|---|---|---|
| Alphabet Efficiency | 36% more compact than hex; human-readable | Fixed 36-symbol set, no padding |
| Environmental Portability | Runs on microcontrollers to servers | no_std + custom WASM allocator |
| Scalable Throughput | No hard size limits | Automatic chunking with 64-byte blocks |
| Version Governance | Protocol evolution without breaking changes | Embedded 6-bit version field |
| Weak-Link Integrity | Detects random corruption per chunk | Fast additive checksum (Σ data) |
| Parallel Speedup | Leverages multicore for large data | Work-stealing chunk distribution |
| Streaming Decoding | Memory-efficient for large messages | State-based incremental assembly |
| URL Safety | No escaping required | Alphanumeric-only output |
| Deterministic Output | Same input → same encoding | No randomization or compression |
| Aspect | Choice | Rationale |
|---|---|---|
| Checksum Strength | Additive sum (weak) | Speed over cryptographic security; suitable for transmission errors, not malicious tampering |
| Chunk Size | 64 bytes default | Balances overhead (14-byte header) vs. flexibility; tunable per use-case |
| Alphabet Size | Base-36 (not 64) | Avoids - and _ symbols; maximizes human readability |
| No Compression | Raw data only | Keeps implementation simple; compression should be applied at application layer |
| Version Encoding | 6-bit limit | Sufficient space for protocol evolution; keeps header compact |
| Encoding | Bits/Char | Overhead (100B) | Human Readable | Chunking | Versioning |
|---|---|---|---|---|---|
| Hex | 4.0 | 100% | ✅ | ❌ | ❌ |
| Base64 | 6.0 | 33% | ❌ | ❌ | ❌ |
| Base64URL | 6.0 | 33% | ❌ | ❌ | ❌ |
| NOL36 | 5.17 | 42% | ✅ | ✅ | ✅ |
| Ascii85 | 6.4 | 25% | ❌ | ❌ | ❌ |
Overhead calculated for 100-byte payload; NOL36 overhead drops significantly for larger data due to chunking.
O(n) time, O(n) spaceO(n) time, O(n) spaceO(chunk_size) per chunk, negligible overheadmin(chunks, cores)localStorage/sessionStorageThe NOL36 encoding logic is compiled to a single nol36.wasm module that exports the following functions:
// Memory Management
nol36_alloc(size: usize) -> *mut u8
nol36_free_alloc(ptr: *mut u8, size: usize)
nol36_free_with_len(ptr: *mut u8, len: usize)
// Encoding
nol36_encode_init(max_chunk_size: usize) -> i32
nol36_encode_feed(input_ptr: *const u8, input_len: usize) -> i32
nol36_encode_finalize(out_ptr_ptr: *mut u8, out_len_ptr: *mut u8) -> i32
// Decoding
nol36_decode_init() -> i32
nol36_decode_feed(chunk_ptr: *const u8, chunk_len: usize) -> i32
nol36_decode_finalize(out_ptr_ptr: *mut u8, out_len_ptr: *mut u8) -> i32
// Diagnostics
nol36_scratch_ptr() -> *mut u8 // Returns 14-byte debug buffer
The module uses a linear memory model with explicit allocation/deallocation. All string data is UTF-8 encoded. The scratch pointer provides diagnostic information about the most recently processed chunk (valid flag, version, index, checksum, data length).
The Rust binding uses wasmtime for efficient WASM execution with full error propagation and thread safety.
Installation:
[dependencies]
nol36 = { path = "bindings/rust" }
API:
use nol36::{init, encode, decode, InitOptions, Nol36Error};
pub fn init(opts: Option<InitOptions>) -> Result<(), Nol36Error>
pub fn encode(data: &[u8]) -> Result<String, Nol36Error>
pub fn decode(encoded: &str) -> Result<Vec<u8>, Nol36Error>
Initialization Options:
pub struct InitOptions {
pub diagnostics: bool, // Enables all diagnostics
pub feed: bool, // Logs feed operation results
pub chunks: bool, // Logs chunk metadata via scratch buffer
}
Usage Example:
fn main() {
// Initialize with diagnostic options
init(Some(InitOptions {
diagnostics: false,
feed: false,
chunks: false,
})).expect("Failed to initialize nol36");
let data = b"Programmable Transaction Blocks...";
let encoded = encode(data).unwrap();
println!("Encoded: {}", encoded); // Base36 string, possibly with '.' delimiters
let decoded = decode(&encoded).unwrap();
assert_eq!(data, decoded.as_slice());
}
Error Handling:
Nol36Error::NotInitialized - Call init() firstNol36Error::InvalidInput - Invalid UTF-8 or allocation failureNol36Error::FunctionError(i32) - WASM function returned error codeNol36Error::WasmRuntime - WASM execution or memory access errorThread Safety: The WASM instance is stored in a OnceLock with a Mutex-wrapped Store, enabling safe concurrent access from multiple threads.
The JavaScript binding provides universal module support for Node.js and browsers, with automatic WASM loading.
Installation:
npm install nol36 # Includes nol36.wasm in lib/
API:
async function init(opts?: {
diagnostics?: boolean,
FEED?: boolean,
CHUNKS?: boolean
}): Promise<void>
function encode(data: Uint8Array): string
function decode(encoded: string): Uint8Array
Usage Example:
const { init, encode, decode } = require('./nol36');
async function main() {
// Initialize WASM with diagnostics
await init({ CHUNKS: false, FEED: false });
const original = new TextEncoder().encode('Programmable Transaction Blocks...');
const encoded = encode(original);
console.log('Encoded:', encoded); // String like "14A9B..."
const decoded = decode(encoded);
console.log('Match:', Buffer.compare(original, decoded) === 0);
}
main().catch(console.error);
WASM Loading Behavior:
fs.readFileSync/nol36.wasm endpointError Handling: Throws standard JavaScript Error objects with descriptive messages for initialization failures, invalid input types, and WASM function errors.
Diagnostics: When enabled, logs chunk metadata to console including version, index, checksum, and data length in real-time during decode operations.
The Python binding uses wasmtime-py for runtime execution, supporting both development and PyInstaller-frozen deployments.
Installation:
pip install nol36 # Installs wasmtime dependency
API:
async def init(opts: Optional[Dict[str, Any]] = None) -> None
def encode(data: bytes) -> str
def decode(encoded: str) -> bytes
Initialization Options:
{
"diagnostics": bool, # Enables all diagnostics
"FEED": bool, # Logs feed operation results
"CHUNKS": bool, # Logs chunk metadata
}
Usage Example:
import asyncio
from nol36 import init, encode, decode
async def main():
# Initialize WASM runtime
await init({"CHUNKS": False, "FEED": False})
original_text = 'Programmable Transaction Blocks...'
original = original_text.encode('utf-8')
encoded = encode(original)
print('Encoded:', encoded) # Base36 string
decoded = decode(encoded)
print('Match:', original == decoded)
if __name__ == "__main__":
asyncio.run(main())
WASM Loading:
nol36.wasm in: lib/, current directory, and PyInstaller's _MEIPASSwasmtime package: pip install wasmtimeError Handling:
RuntimeError - WASM initialization failures, function errorsTypeError - Invalid input types (must be bytes and str)FileNotFoundError - WASM binary not foundImportError - wasmtime not installedDiagnostics: Prints chunk analysis to stdout when enabled, showing version, index, checksum, and data length for each processed chunk.
OnceLock + Mutex; JavaScript/Python bindings use single-threaded WASM runtimenol36.wasm bytecode, ensuring bit-identical output across platformsResult, try/catch, try/except)Threat Model: NOL36 is designed for integrity, not confidentiality or authenticity.
NOL36 occupies a unique design point: more compact than hex, more portable than base64, and more robust than either through integrated chunking and versioning. Its no_std foundation and WASM-native design make ideal for the emerging edge computing landscape where efficiency, readability, and reliability converge. The unified WASM core ensures consistent behavior across Rust, JavaScript, and Python environments while preserving idiomatic APIs for each ecosystem.