Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions gleam.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ version = "1.0.0"

description = "A Gleam implementation of the CBOR standard"
licences = ["Apache-2.0"]
repository = { type = "github", user = "Beaudidly", repo = "https://github.com/Beaudidly/gbor" }
repository = { type = "github", user = "Beaudidly", repo = "gbor" }

[dependencies]
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
Expand All @@ -12,5 +12,3 @@ gleam_erlang = ">= 1.2.0 and < 2.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
simplifile = ">= 2.3.0 and < 3.0.0"
gleam_json = ">= 3.0.1 and < 4.0.0"
9 changes: 9 additions & 0 deletions src/gbor.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
//// Module where we can find the base types used for the CBOR values

/// The base types used for representing CBOR values with Gleam types, used
/// as a primary output from the decoding process, as well as input for the
/// encoding process.
///
/// > **Note**: This type may become opaque in future major types as we evaluate
/// > the API needs for this package.
///
pub type CBOR {
CBInt(Int)
CBString(String)
Expand Down
72 changes: 53 additions & 19 deletions src/gbor/decode.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//// Tools for decoding binary CBOR data into Gleam types

import gleam/bit_array
import gleam/bool
import gleam/dynamic
Expand All @@ -12,6 +14,7 @@ import ieee_float as i

import gbor as g

/// The error type for decoding CBOR data into Gleam types
pub type CborDecodeError {
DynamicDecodeError(List(gdd.DecodeError))
MajorTypeError(Int)
Expand All @@ -20,6 +23,7 @@ pub type CborDecodeError {
UnassignedError
}

/// Convert a CBOR Gleam value to a dynamic value for use with `gleam/dynamic/decode`
pub fn cbor_to_dynamic(cbor: g.CBOR) -> dynamic.Dynamic {
case cbor {
g.CBInt(v) -> dynamic.int(v)
Expand All @@ -38,6 +42,21 @@ pub fn cbor_to_dynamic(cbor: g.CBOR) -> dynamic.Dynamic {
}
}

/// Decode a tagged CBOR value.
///
/// Provided tag is the expected tag number for the value, and the decoder is run
/// on the value corresponding to the tag.
///
/// For example, for a CBOR value with a tag number of `0`, the expected data item
/// is a text string representing a standard time string, so one would call:
///
/// ```gleam
/// import gleam/dynamic/decode as gdd
/// tagged_decoder(0, gdd.string, "")
/// ```
///
/// Reference: [RFC 8949 : 3.4 Tagging of Items](https://www.rfc-editor.org/rfc/rfc8949.html#name-tagging-of-items)
///
pub fn tagged_decoder(
expected_tag: Int,
decoder: gdd.Decoder(a),
Expand All @@ -58,7 +77,27 @@ pub fn tagged_decoder(
gdd.at([2], decoder)
}

pub fn decode(data: BitArray) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
/// Decode a CBOR value from a bit array
///
/// This function is the main entry point for decoding CBOR data into Gleam types.
///
/// It takes a bit array and returns a result containing the decoded CBOR value
///
pub fn decode(data: BitArray) -> Result(g.CBOR, CborDecodeError) {
case decode_helper(data) {
Ok(#(v, <<>>)) -> Ok(v)
Ok(#(_, rest)) ->
Error(
DynamicDecodeError(gdd.decode_error(
expected: "Expected data, but got more",
found: dynamic.bit_array(rest),
)),
)
Error(e) -> Error(e)
}
}

fn decode_helper(data: BitArray) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
case data {
<<0:3, min:5, rest:bits>> -> decode_uint(min, rest)
<<1:3, min:5, rest:bits>> -> decode_int(min, rest)
Expand Down Expand Up @@ -105,7 +144,7 @@ fn decode_uint(
}
}

pub fn decode_int(
fn decode_int(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand All @@ -127,7 +166,7 @@ pub fn decode_int(
Ok(#(g.CBInt(-1 - v), rest))
}

pub fn decode_float_or_simple_value(
fn decode_float_or_simple_value(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand Down Expand Up @@ -155,7 +194,7 @@ pub fn decode_float_or_simple_value(
}
}

pub fn decode_float(
fn decode_float(
data: BitArray,
rest: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand Down Expand Up @@ -186,15 +225,15 @@ pub fn decode_float(
}
}

pub fn decode_bytes(
fn decode_bytes(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
use #(v, rest) <- result.try(decode_bytes_helper(min, data))
Ok(#(g.CBBinary(v), rest))
}

pub fn decode_string(
fn decode_string(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand All @@ -214,7 +253,7 @@ pub fn decode_string(
Ok(#(v, rest))
}

pub fn decode_bytes_helper(
fn decode_bytes_helper(
min: Int,
data: BitArray,
) -> Result(#(BitArray, BitArray), CborDecodeError) {
Expand All @@ -238,7 +277,7 @@ pub fn decode_bytes_helper(
}
}

pub fn decode_array(
fn decode_array(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand Down Expand Up @@ -268,21 +307,21 @@ pub fn decode_array(
Ok(#(g.CBArray(list.reverse(values)), rest))
}

pub fn decode_array_helper(
fn decode_array_helper(
data: BitArray,
n: Int,
acc: List(g.CBOR),
) -> Result(#(List(g.CBOR), BitArray), CborDecodeError) {
case n {
0 -> Ok(#(acc, data))
_ -> {
use #(v, rest_data) <- result.try(decode(data))
use #(v, rest_data) <- result.try(decode_helper(data))
decode_array_helper(rest_data, n - 1, [v, ..acc])
}
}
}

pub fn decode_map(
fn decode_map(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand Down Expand Up @@ -333,7 +372,7 @@ pub fn decode_map(
Ok(#(map, rest))
}

pub fn decode_tagged(
fn decode_tagged(
min: Int,
data: BitArray,
) -> Result(#(g.CBOR, BitArray), CborDecodeError) {
Expand All @@ -352,15 +391,10 @@ pub fn decode_tagged(
}
})

use #(tag_value, rest) <- result.try(decode(rest))
use #(tag_value, rest) <- result.try(decode_helper(rest))

Ok(#(g.CBTagged(tag_num, tag_value), rest))
}

@external(erlang, "erl_gbor", "to_tagged")
pub fn ffi_to_tagged(tag: Int, value: dynamic.Dynamic) -> dynamic.Dynamic

@external(erlang, "erl_gbor", "check_tagged")
pub fn ffi_check_tagged(
tagged: dynamic.Dynamic,
) -> Result(#(Int, dynamic.Dynamic), String)
fn ffi_to_tagged(tag: Int, value: dynamic.Dynamic) -> dynamic.Dynamic
57 changes: 6 additions & 51 deletions src/gbor/encode.gleam
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//// Module where we can find the functions used for getting the CBOR binary representation of a value

import gleam/bit_array
import gleam/list
import gleam/result
Expand All @@ -11,46 +13,12 @@ pub type EncodeError {
EncodeError(String)
}

pub fn int(value: Int) -> g.CBOR {
g.CBInt(value)
}

pub fn map(value: List(#(g.CBOR, g.CBOR))) -> g.CBOR {
g.CBMap(value)
}

pub fn string(value: String) -> g.CBOR {
g.CBString(value)
}

pub fn array(value: List(g.CBOR)) -> g.CBOR {
g.CBArray(value)
}

pub fn bool(value: Bool) -> g.CBOR {
g.CBBool(value)
}

pub fn null() -> g.CBOR {
g.CBNull
}

pub fn undefined() -> g.CBOR {
g.CBUndefined
}

pub fn float(value: Float) -> g.CBOR {
g.CBFloat(value)
}

pub fn binary(value: BitArray) -> g.CBOR {
g.CBBinary(value)
}

/// Encode a CBOR value to a bit array
pub fn to_bit_array(value: g.CBOR) -> Result(BitArray, EncodeError) {
case value {
g.CBInt(v) if v >= 0 -> uint_encode(v)
g.CBInt(v) if v < 0 -> int_encode(v)
g.CBInt(v) -> uint_encode(v)
g.CBFloat(v) -> Ok(float_encode(v))
g.CBBinary(v) -> binary_encode(BinaryEncoding(v))
g.CBString(v) -> binary_encode(StringEncoding(v))
Expand All @@ -60,13 +28,10 @@ pub fn to_bit_array(value: g.CBOR) -> Result(BitArray, EncodeError) {
g.CBTagged(t, v) -> tagged_encode(t, v)
g.CBNull -> Ok(null_encode())
g.CBUndefined -> Ok(undefined_encode())
v -> {
Error(EncodeError("Unknown CBOR value: " <> string.inspect(v)))
}
}
}

pub fn uint_encode(value: Int) -> Result(BitArray, EncodeError) {
fn uint_encode(value: Int) -> Result(BitArray, EncodeError) {
case value {
v if v < 24 -> Ok(<<0:3, v:5>>)
// TODO verify limits
Expand All @@ -78,7 +43,7 @@ pub fn uint_encode(value: Int) -> Result(BitArray, EncodeError) {
}
}

pub fn int_encode(value: Int) -> Result(BitArray, EncodeError) {
fn int_encode(value: Int) -> Result(BitArray, EncodeError) {
let value = { value * -1 } - 1
case value {
v if v < 24 -> Ok(<<1:3, v:5>>)
Expand Down Expand Up @@ -167,16 +132,6 @@ fn map_encode(value: List(#(g.CBOR, g.CBOR))) -> Result(BitArray, EncodeError) {
}),
)

//let data = list.try

//let data =
// list.fold_right(value, <<>>, fn(acc, a) {
// let k_data = to_bit_array(a.0)
// let v_data = to_bit_array(a.1)

// bit_array.concat([k_data, v_data, acc])
// })

case n_pairs {
v if v < 24 -> Ok(<<5:3, v:5, data:bits>>)
v if v < 0x100 -> Ok(<<5:3, 25:5, v:8, data:bits>>)
Expand Down
11 changes: 1 addition & 10 deletions src/gbor/erl_gbor.erl
Original file line number Diff line number Diff line change
@@ -1,15 +1,6 @@
-module(erl_gbor).

-export([to_tagged/2, check_tagged/1]).
-export([to_tagged/2]).

to_tagged(Tag, Value) ->
{cbor_tagged__, Tag, Value}.


check_tagged(Tagged) ->
case Tagged of
{cbor_tagged__, Tag, Value} ->
{ok, {Tag, Value}};
_ ->
{error, invalid_tagged_value}
end.
4 changes: 2 additions & 2 deletions test/round_trip_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ pub fn decode_dynamic_test() {
"A2646E616D6574323031332D30332D32315432303A30343A30305A63646F62C074323031332D30332D32315432303A30343A30305A",
)

let assert Ok(#(cbor_val, <<>>)) = d.decode(bin)
let assert Ok(cbor_val) = d.decode(bin)
let dyn_val = d.cbor_to_dynamic(cbor_val)

let decoder = {
Expand All @@ -272,7 +272,7 @@ pub fn decode_dynamic_test() {

fn round_trip(expected: g.CBOR, hex: String) -> Result(Nil, String) {
let assert Ok(binary) = bit_array.base16_decode(hex)
let assert Ok(#(v, <<>>)) = d.decode(binary)
let assert Ok(v) = d.decode(binary)
use <- bool.guard(
v != expected,
Error(
Expand Down