├── test ├── test_helper.exs ├── cbor_test.exs └── cbor │ ├── encoder_test.exs │ └── decoder_test.exs ├── lib ├── cbor │ ├── tag.ex │ ├── utils.ex │ ├── encoder.ex │ └── decoder.ex └── cbor.ex ├── .formatter.exs ├── .gitignore ├── LICENSE.md ├── mix.exs ├── mix.lock └── README.md /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /test/cbor_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CBORTest do 2 | use ExUnit.Case 3 | doctest CBOR 4 | end 5 | -------------------------------------------------------------------------------- /lib/cbor/tag.ex: -------------------------------------------------------------------------------- 1 | defmodule CBOR.Tag do 2 | @enforce_keys [:tag, :value] 3 | defstruct [:tag, :value] 4 | end -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | # Used by "mix format" 2 | [ 3 | inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] 4 | ] 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # The directory Mix will write compiled artifacts to. 2 | /_build/ 3 | 4 | # If you run "mix test --cover", coverage assets end up here. 5 | /cover/ 6 | 7 | # The directory Mix downloads your dependencies sources to. 8 | /deps/ 9 | 10 | # Where third-party dependencies like ExDoc output generated docs. 11 | /doc/ 12 | 13 | # Ignore .fetch files in case you like to edit your project deps locally. 14 | /.fetch 15 | 16 | # If the VM crashes, it generates a dump, let's ignore it too. 17 | erl_crash.dump 18 | 19 | # Also ignore archive artifacts (built via "mix archive.build"). 20 | *.ez 21 | 22 | # Ignore package tarball (built via "mix hex.build"). 23 | cbor-*.tar 24 | 25 | # Temporary files, for example, from tests. 26 | /tmp/ 27 | -------------------------------------------------------------------------------- /lib/cbor/utils.ex: -------------------------------------------------------------------------------- 1 | defmodule CBOR.Utils do 2 | def encode_head(mt, val, acc) when val < 24 do 3 | <> 4 | end 5 | 6 | def encode_head(mt, val, acc) when val < 0x100 do 7 | <> 8 | end 9 | 10 | def encode_head(mt, val, acc) when val < 0x10000 do 11 | <> 12 | end 13 | 14 | def encode_head(mt, val, acc) when val < 0x100000000 do 15 | <> 16 | end 17 | 18 | def encode_head(mt, val, acc) when val < 0x10000000000000000 do 19 | <> 20 | end 21 | 22 | def encode_string(mt, s, acc) when byte_size(s) < 0x10000000000000000 do 23 | <> 24 | end 25 | 26 | def encode_string(_mt, s, acc) do 27 | <> 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # MIT License 2 | 3 | Copyright (c) 2019 Thomas Cioppettini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule Cbor.MixProject do 2 | use Mix.Project 3 | 4 | @source_url "https://github.com/scalpel-software/cbor" 5 | @version "1.0.1" 6 | 7 | def project do 8 | [ 9 | app: :cbor, 10 | version: @version, 11 | elixir: "~> 1.0", 12 | start_permanent: Mix.env() == :prod, 13 | package: package(), 14 | deps: deps(), 15 | docs: docs() 16 | ] 17 | end 18 | 19 | def application do 20 | [ 21 | extra_applications: [:logger] 22 | ] 23 | end 24 | 25 | defp deps do 26 | [ 27 | {:ex_doc, ">= 0.0.0", only: :dev, runtime: false} 28 | ] 29 | end 30 | 31 | defp package do 32 | [ 33 | description: "Implementation of RFC 7049 (Concise Binary Object Representation)", 34 | maintainers: ["tomciopp"], 35 | licenses: ["MIT"], 36 | links: %{"GitHub" => @source_url} 37 | ] 38 | end 39 | 40 | defp docs do 41 | [ 42 | extras: [ 43 | "LICENSE.md": [title: "License"], 44 | "README.md": [title: "Overview"] 45 | ], 46 | main: "readme", 47 | source_url: @source_url, 48 | source_ref: "#v{@version}", 49 | formatters: ["html"] 50 | ] 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /mix.lock: -------------------------------------------------------------------------------- 1 | %{ 2 | "cbor": {:hex, :cbor, "0.1.8", "8996d095d80b69d7a4a2c47799e6c7ee7f195192cae14395babf1c97f2c4a999", [:mix], [], "hexpm"}, 3 | "earmark": {:hex, :earmark, "1.3.2", "b840562ea3d67795ffbb5bd88940b1bed0ed9fa32834915125ea7d02e35888a5", [:mix], [], "hexpm", "e3be2bc3ae67781db529b80aa7e7c49904a988596e2dbff897425b48b3581161"}, 4 | "earmark_parser": {:hex, :earmark_parser, "1.4.15", "b29e8e729f4aa4a00436580dcc2c9c5c51890613457c193cc8525c388ccb2f06", [:mix], [], "hexpm", "044523d6438ea19c1b8ec877ec221b008661d3c27e3b848f4c879f500421ca5c"}, 5 | "ex_doc": {:hex, :ex_doc, "0.25.2", "4f1cae793c4d132e06674b282f1d9ea3bf409bcca027ddb2fe177c4eed6a253f", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5b0c172e87ac27f14dfd152d52a145238ec71a95efbf29849550278c58a393d6"}, 6 | "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, 7 | "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, 8 | "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, 9 | "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, 10 | } 11 | -------------------------------------------------------------------------------- /test/cbor/encoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CBOR.EncoderTest do 2 | use ExUnit.Case, async: true 3 | 4 | defp reconstruct(value) do 5 | value |> CBOR.encode() |> CBOR.decode() 6 | end 7 | 8 | test "given the value of true" do 9 | assert reconstruct(true) == {:ok, true, ""} 10 | end 11 | 12 | test "given the value of false" do 13 | assert reconstruct(false) == {:ok, false, ""} 14 | end 15 | 16 | test "given the value of nil" do 17 | assert reconstruct(nil) == {:ok, nil, ""} 18 | end 19 | 20 | test "given the value of undefined" do 21 | assert reconstruct(:__undefined__) == {:ok, :__undefined__, ""} 22 | end 23 | 24 | test "given another atom (converts to string)" do 25 | assert reconstruct(:another_atom) == {:ok, "another_atom", ""} 26 | end 27 | 28 | test "given a large string" do 29 | assert reconstruct(String.duplicate("a", 20000)) == {:ok, String.duplicate("a", 20000), ""} 30 | end 31 | 32 | test "given an integer" do 33 | assert reconstruct(1) == {:ok, 1, ""} 34 | end 35 | 36 | test "given an bignum" do 37 | assert reconstruct(51090942171709440000) == {:ok, 51090942171709440000, ""} 38 | end 39 | 40 | test "given an negative bignum" do 41 | assert reconstruct(-51090942171709440000) == {:ok, -51090942171709440000, ""} 42 | end 43 | 44 | test "given a list with several items" do 45 | assert reconstruct([1,2,3,4,5,6,7,8]) == {:ok, [1,2,3,4,5,6,7,8], ""} 46 | end 47 | 48 | test "given an empty list" do 49 | assert reconstruct([]) == {:ok, [], ""} 50 | end 51 | 52 | test "given a complex nested list" do 53 | assert reconstruct([1, [2, 3], [4, 5]]) == {:ok, [1, [2, 3], [4, 5]], ""} 54 | end 55 | 56 | test "given a tuple, it converts it to a list" do 57 | assert reconstruct({}) == {:ok, [], ""} 58 | end 59 | 60 | test "given a tuple with several items" do 61 | assert reconstruct({1,2,3,4,5,6,7,8}) == {:ok, [1,2,3,4,5,6,7,8], ""} 62 | end 63 | 64 | test "given a complex nested tuple" do 65 | assert reconstruct({1, {2, 3}, {4, 5}}) == {:ok, [1, [2, 3], [4, 5]], ""} 66 | end 67 | 68 | test "given an empty MapSet" do 69 | assert reconstruct(MapSet.new()) == {:ok, [], ""} 70 | end 71 | 72 | test "given a MapSet" do 73 | assert reconstruct(MapSet.new([1,2,3])) == {:ok, [1,2,3], ""} 74 | end 75 | 76 | test "given a range" do 77 | assert reconstruct(1..10) == {:ok, [1,2,3,4,5,6,7,8,9,10], ""} 78 | end 79 | 80 | test "an empty map" do 81 | assert reconstruct(%{}) == {:ok, %{}, ""} 82 | end 83 | 84 | test "a map with atom keys and values" do 85 | assert reconstruct(%{foo: :bar, baz: :quux}) == {:ok, %{"foo" => "bar", "baz" => "quux"}, ""} 86 | end 87 | 88 | test "complex maps" do 89 | assert reconstruct(%{"a" => 1, "b" => [2, 3]}) == {:ok, %{"a" => 1, "b" => [2, 3]}, ""} 90 | end 91 | 92 | test "tagged infinity" do 93 | assert reconstruct(%CBOR.Tag{tag: :float, value: :inf}) == {:ok, %CBOR.Tag{tag: :float, value: :inf}, ""} 94 | end 95 | 96 | test "tagged negative infinity" do 97 | assert reconstruct(%CBOR.Tag{tag: :float, value: :"-inf"}) == {:ok, %CBOR.Tag{tag: :float, value: :"-inf"}, ""} 98 | end 99 | 100 | test "tagged nan" do 101 | assert reconstruct(%CBOR.Tag{tag: :float, value: :nan}) == {:ok, %CBOR.Tag{tag: :float, value: :nan}, ""} 102 | end 103 | 104 | test "given a URI" do 105 | uri = %URI{ 106 | authority: "www.example.com", 107 | fragment: nil, 108 | host: "www.example.com", 109 | path: nil, 110 | port: 80, 111 | query: nil, 112 | scheme: "http", 113 | userinfo: nil 114 | } 115 | 116 | assert reconstruct(uri) == {:ok, uri, ""} 117 | end 118 | 119 | test "given 0.0" do 120 | assert reconstruct(0.0) == {:ok, 0.0, ""} 121 | end 122 | 123 | test "given 0.1" do 124 | assert reconstruct(0.1) == {:ok, 0.1, ""} 125 | end 126 | 127 | test "given 1.0" do 128 | assert reconstruct(0.0) == {:ok, 0.0, ""} 129 | end 130 | 131 | test "given 1.1" do 132 | assert reconstruct(0.1) == {:ok, 0.1, ""} 133 | end 134 | 135 | test "more complex float" do 136 | assert reconstruct(123.1237987) == {:ok, 123.1237987, ""} 137 | end 138 | 139 | test "given a bignum" do 140 | assert reconstruct(2432902008176640000) == {:ok, 2432902008176640000, ""} 141 | end 142 | 143 | test "given a datetime" do 144 | assert reconstruct(~U[2013-03-21 20:04:00Z]) == {:ok, ~U[2013-03-21 20:04:00Z], ""} 145 | end 146 | 147 | test "given a naive datetime" do 148 | assert reconstruct(~N[2019-07-22 17:17:40.564490]) == {:ok, ~U[2019-07-22 17:17:40.564490Z], ""} 149 | end 150 | 151 | test "given a date" do 152 | assert reconstruct(~D[2000-01-01]) == {:ok, ~D[2000-01-01], ""} 153 | end 154 | 155 | test "given a time" do 156 | assert reconstruct(~T[23:00:07.001]) == {:ok, ~T[23:00:07.001], ""} 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CBOR 2 | 3 | [![Module Version](https://img.shields.io/hexpm/v/cbor.svg)](https://hex.pm/packages/cbor) 4 | [![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/cbor/) 5 | [![Total Download](https://img.shields.io/hexpm/dt/cbor.svg)](https://hex.pm/packages/cbor) 6 | [![License](https://img.shields.io/hexpm/l/cbor.svg)](https://github.com/scalpel-software/cbor/blob/master/LICENSE.md) 7 | [![Last Updated](https://img.shields.io/github/last-commit/scalpel-software/cbor.svg)](https://github.com/scalpel-software/cbor/commits/master) 8 | 9 | Implementation of RFC 7049 [CBOR](http://cbor.io) (Concise Binary 10 | Object Representation) for Elixir. 11 | 12 | This is a fork of [excbor](https://github.com/cabo/excbor) which modernizes 13 | the codebase, and makes decisions on handling data types that the original library had punted on. 14 | 15 | ## Migrating from the previous version 16 | 17 | This library is a fork of the no longer maintained excbor project. 18 | 19 | For those migrating from previous versions of this library there are breaking changes that you should be aware of. 20 | 21 | The module `Cbor` has been renamed to `CBOR`. 22 | 23 | CBOR.decode will return a three item tuple of the form `{:ok, decoded, rest}`, instead of returning the decoded object. In the wild there are APIs that concat CBOR objects together. The `rest` variable includes any leftover information from the decoding operation in case you need to decode multiple objects. 24 | 25 | Atoms will be encoded/decoded as strings, except for the special case of `:__undefined__` which has no direct translation to elixir but has semantic meaning in CBOR. 26 | 27 | Elixir/Erlang does not have a concept of infinity, negative infinity or NaN. In order to encode or decode these values we will return a struct of the form `%CBOR.Tag{tag: :float, value: (:inf|:"-inf"|:nan)}` 28 | 29 | If you want to encode a raw binary value, you can use the `CBOR.Tag` struct with a tag of `:bytes` and the binary as the `:value` field. 30 | 31 | ## Installation 32 | 33 | ```elixir 34 | def deps do 35 | [ 36 | {:cbor, "~> 1.0.0"} 37 | ] 38 | end 39 | ``` 40 | 41 | ## Usage 42 | 43 | This library follows the standard API for CBOR libraries by exposing two methods 44 | on the CBOR module `CBOR.encode/1` and `CBOR.decode/1`. 45 | 46 | ### Encoding 47 | 48 | ```elixir 49 | iex(1)> CBOR.encode([1, [2, 3]]) 50 | <<130, 1, 130, 2, 3>> 51 | ``` 52 | 53 | ### Decoding 54 | 55 | ```elixir 56 | iex(2)> CBOR.decode(<<130, 1, 130, 2, 3>>) 57 | {:ok, [1, [2, 3]], ""} 58 | ``` 59 | 60 | ## Design Notes 61 | 62 | Given that Elixir has more available data types than are supported in CBOR, decisions were made so that encoding complex data structures succeed without throwing errors. My thoughts are collected below so you can understand why encoding and decoding of a value does not necessarily return exactly the same value. 63 | 64 | ### Atoms 65 | 66 | The only atoms that will be directly encoded are `true`, `false` `nil` and `__undefined__`. Every other atom will be converted to a string before being encoded. We surround undefined with double underscores so that you only encode an undefined value when you clearly intend to do so. 67 | 68 | ### Keyword List, MapSet, Range, Tuple 69 | 70 | All of the above data structures are converted to Lists before being encoded. This ensures that there is no data lost when encoding and decoding. 71 | 72 | ### NaiveDateTime 73 | 74 | NaiveDateTime will be treated as if they are UTC. 75 | 76 | ### Special Values 77 | 78 | Elixir and erlang have no concept of infinity, negative infinity and NaN. If you want to encode those values, we have a special struct `CBOR.Tag` which you can use to represent those values. 79 | 80 | ```elixir 81 | %CBOR.Tag{tag: :float, value: :inf} 82 | 83 | %CBOR.Tag{tag: :float, value: :"-inf"} 84 | 85 | %CBOR.Tag{tag: :float, value: :nan} 86 | ``` 87 | 88 | `CBOR.Tag` is also useful if you want to extend `CBOR` for internal applications 89 | 90 | ## Custom Encoding 91 | 92 | If you want to encode something that is not supported out of the box you can implement the `CBOR.Encoder` protocol for the module. You only have to implement a single `CBOR.Encoder.encode_into/2` function. An example for encoding a Money struct is given below. 93 | 94 | ```elixir 95 | defimpl CBOR.Encoder, for: Money do 96 | def encode_into(money, acc) do 97 | money |> Money.to_string() |> CBOR.Encoder.encode_into(acc) 98 | end 99 | end 100 | ``` 101 | 102 | ### Documentation 103 | 104 | Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc) 105 | and published on [HexDocs](https://hexdocs.pm). Once published, the docs can 106 | be found at [https://hexdocs.pm/cbor](https://hexdocs.pm/cbor). 107 | 108 | 109 | ## Copyright and License 110 | 111 | Copyright (c) 2019 Thomas Cioppettini 112 | 113 | This work is free. You can redistribute it and/or modify it under the 114 | terms of the MIT License. See the [LICENSE.md](./LICENSE.md) file for more details. 115 | -------------------------------------------------------------------------------- /lib/cbor.ex: -------------------------------------------------------------------------------- 1 | defmodule CBOR do 2 | @moduledoc """ 3 | The Concise Binary Object Representation (CBOR) is a data format 4 | whose design goals include the possibility of extremely small code 5 | size, fairly small message size, and extensibility without the need 6 | for version negotiation. These design goals make it different from 7 | earlier binary serializations such as ASN.1 and MessagePack. 8 | 9 | The objectives of CBOR, roughly in decreasing order of importance are: 10 | 11 | 1. The representation must be able to unambiguously encode most 12 | common data formats used in Internet standards. 13 | 14 | * It must represent a reasonable set of basic data types and 15 | structures using binary encoding. "Reasonable" here is 16 | largely influenced by the capabilities of JSON, with the major 17 | addition of binary byte strings. The structures supported are 18 | limited to arrays and trees; loops and lattice-style graphs 19 | are not supported. 20 | 21 | * There is no requirement that all data formats be uniquely 22 | encoded; that is, it is acceptable that the number "7" might 23 | be encoded in multiple different ways. 24 | 25 | 2. The code for an encoder or decoder must be able to be compact in 26 | order to support systems with very limited memory, processor 27 | power, and instruction sets. 28 | 29 | * An encoder and a decoder need to be implementable in a very 30 | small amount of code (for example, in class 1 constrained 31 | nodes as defined in [CNN-TERMS]). 32 | 33 | * The format should use contemporary machine representations of 34 | data (for example, not requiring binary-to-decimal 35 | conversion). 36 | 37 | 3. Data must be able to be decoded without a schema description. 38 | 39 | * Similar to JSON, encoded data should be self-describing so 40 | that a generic decoder can be written. 41 | 42 | 4. The serialization must be reasonably compact, but data 43 | compactness is secondary to code compactness for the encoder and 44 | decoder. 45 | 46 | * "Reasonable" here is bounded by JSON as an upper bound in 47 | size, and by implementation complexity maintaining a lower 48 | bound. Using either general compression schemes or extensive 49 | bit-fiddling violates the complexity goals. 50 | 51 | 5. The format must be applicable to both constrained nodes and high- 52 | volume applications. 53 | 54 | * This means it must be reasonably frugal in CPU usage for both 55 | encoding and decoding. This is relevant both for constrained 56 | nodes and for potential usage in applications with a very high 57 | volume of data. 58 | 59 | 6. The format must support all JSON data types for conversion to and 60 | from JSON. 61 | 62 | * It must support a reasonable level of conversion as long as 63 | the data represented is within the capabilities of JSON. It 64 | must be possible to define a unidirectional mapping towards 65 | JSON for all types of data. 66 | 67 | 7. The format must be extensible, and the extended data must be 68 | decodable by earlier decoders. 69 | 70 | * The format is designed for decades of use. 71 | 72 | * The format must support a form of extensibility that allows 73 | fallback so that a decoder that does not understand an 74 | extension can still decode the message. 75 | 76 | * The format must be able to be extended in the future by later 77 | IETF standards. 78 | """ 79 | 80 | @doc """ 81 | Returns a binary encoding of the data in a format 82 | that can be interpreted by other CBOR libraries. 83 | 84 | ## Examples 85 | 86 | iex> CBOR.encode(["Hello", "World!"]) 87 | <<130, 101, 72, 101, 108, 108, 111, 102, 87, 111, 114, 108, 100, 33>> 88 | 89 | iex> CBOR.encode([1, [2, 3]]) 90 | <<130, 1, 130, 2, 3>> 91 | 92 | iex> CBOR.encode(%{"a" => 1, "b" => [2, 3]}) 93 | <<162, 97, 97, 1, 97, 98, 130, 2, 3>> 94 | 95 | """ 96 | @spec encode(any()) :: binary() 97 | def encode(value), do: CBOR.Encoder.encode_into(value, <<>>) 98 | 99 | @doc """ 100 | Converts a CBOR encoded binary into native elixir data structures 101 | 102 | ## Examples 103 | 104 | iex> CBOR.decode(<<130, 101, 72, 101, 108, 108, 111, 102, 87, 111, 114, 108, 100, 33>>) 105 | {:ok, ["Hello", "World!"], ""} 106 | 107 | iex> CBOR.decode(<<130, 1, 130, 2, 3>>) 108 | {:ok, [1, [2, 3]], ""} 109 | 110 | iex> CBOR.decode(<<162, 97, 97, 1, 97, 98, 130, 2, 3>>) 111 | {:ok, %{"a" => 1, "b" => [2, 3]}, ""} 112 | 113 | """ 114 | @spec decode(binary()) :: {:ok, any(), binary()} | {:error, atom} 115 | def decode(binary) do 116 | try do 117 | perform_decoding(binary) 118 | rescue 119 | FunctionClauseError -> {:error, :cbor_function_clause_error} 120 | MatchError -> {:error, :cbor_match_error} 121 | end 122 | end 123 | 124 | defp perform_decoding(binary) when is_binary(binary) do 125 | case CBOR.Decoder.decode(binary) do 126 | {value, rest} -> {:ok, value, rest} 127 | _other -> {:error, :cbor_decoder_error} 128 | end 129 | end 130 | 131 | defp perform_decoding(_value), do: {:error, :cannot_decode_non_binary_values} 132 | end 133 | -------------------------------------------------------------------------------- /lib/cbor/encoder.ex: -------------------------------------------------------------------------------- 1 | defprotocol CBOR.Encoder do 2 | @doc """ 3 | Converts an Elixir data type to its representation in CBOR. 4 | """ 5 | 6 | def encode_into(element, acc) 7 | end 8 | 9 | defimpl CBOR.Encoder, for: Atom do 10 | def encode_into(false, acc), do: <> 11 | def encode_into(true, acc), do: <> 12 | def encode_into(nil, acc), do: <> 13 | def encode_into(:__undefined__, acc), do: <> 14 | def encode_into(v, acc), do: CBOR.Utils.encode_string(3, Atom.to_string(v), acc) 15 | end 16 | 17 | defimpl CBOR.Encoder, for: BitString do 18 | def encode_into(s, acc), do: CBOR.Utils.encode_string(3, s, acc) 19 | end 20 | 21 | defimpl CBOR.Encoder, for: CBOR.Tag do 22 | def encode_into(%CBOR.Tag{tag: :bytes, value: s}, acc) do 23 | CBOR.Utils.encode_string(2, s, acc) 24 | end 25 | 26 | def encode_into(%CBOR.Tag{tag: :float, value: :inf}, acc) do 27 | <> 28 | end 29 | 30 | def encode_into(%CBOR.Tag{tag: :float, value: :"-inf"}, acc) do 31 | <> 32 | end 33 | 34 | def encode_into(%CBOR.Tag{tag: :float, value: :nan}, acc) do 35 | <> 36 | end 37 | 38 | def encode_into(%CBOR.Tag{tag: :simple, value: val}, acc) when val < 0x100 do 39 | CBOR.Utils.encode_head(7, val, acc) 40 | end 41 | 42 | def encode_into(%CBOR.Tag{tag: tag, value: val}, acc) do 43 | CBOR.Encoder.encode_into(val, CBOR.Utils.encode_head(6, tag, acc)) 44 | end 45 | end 46 | 47 | defimpl CBOR.Encoder, for: Date do 48 | def encode_into(time, acc) do 49 | CBOR.Encoder.encode_into( 50 | Date.to_iso8601(time), 51 | CBOR.Utils.encode_head(6, 0, acc) 52 | ) 53 | end 54 | end 55 | 56 | defimpl CBOR.Encoder, for: DateTime do 57 | def encode_into(datetime, acc) do 58 | CBOR.Encoder.encode_into( 59 | DateTime.to_iso8601(datetime), 60 | CBOR.Utils.encode_head(6, 0, acc) 61 | ) 62 | end 63 | end 64 | 65 | defimpl CBOR.Encoder, for: Float do 66 | def encode_into(x, acc), do: <> 67 | end 68 | 69 | defimpl CBOR.Encoder, for: Integer do 70 | def encode_into(i, acc) when i >= 0 and i < 0x10000000000000000 do 71 | CBOR.Utils.encode_head(0, i, acc) 72 | end 73 | 74 | def encode_into(i, acc) when i < 0 and i >= -0x10000000000000000 do 75 | CBOR.Utils.encode_head(1, -i - 1, acc) 76 | end 77 | 78 | def encode_into(i, acc) when i >= 0, do: encode_as_bignum(i, 2, acc) 79 | def encode_into(i, acc) when i < 0, do: encode_as_bignum(-i - 1, 3, acc) 80 | 81 | defp encode_as_bignum(i, tag, acc) do 82 | CBOR.Utils.encode_string( 83 | 2, 84 | :binary.encode_unsigned(i), 85 | CBOR.Utils.encode_head(6, tag, acc) 86 | ) 87 | end 88 | end 89 | 90 | defimpl CBOR.Encoder, for: List do 91 | def encode_into([], acc), do: <> 92 | 93 | def encode_into(list, acc) when length(list) < 0x10000000000000000 do 94 | Enum.reduce(list, CBOR.Utils.encode_head(4, length(list), acc), fn(v, acc) -> 95 | CBOR.Encoder.encode_into(v, acc) 96 | end) 97 | end 98 | 99 | def encode_into(list, acc) do 100 | Enum.reduce(list, <>, fn(v, acc) -> 101 | CBOR.Encoder.encode_into(v, acc) 102 | end) <> <<0xff>> 103 | end 104 | end 105 | 106 | defimpl CBOR.Encoder, for: Map do 107 | def encode_into(map, acc) when map_size(map) == 0, do: <> 108 | 109 | def encode_into(map, acc) when map_size(map) < 0x10000000000000000 do 110 | Enum.reduce(map, CBOR.Utils.encode_head(5, map_size(map), acc), fn({k, v}, subacc) -> 111 | CBOR.Encoder.encode_into(v, CBOR.Encoder.encode_into(k, subacc)) 112 | end) 113 | end 114 | 115 | def encode_into(map, acc) do 116 | Enum.reduce(map, <>, fn({k, v}, subacc) -> 117 | CBOR.Encoder.encode_into(v, CBOR.Encoder.encode_into(k, subacc)) 118 | end) <> <<0xff>> 119 | end 120 | end 121 | 122 | # We convert MapSets into lists since there is no 'set' representation 123 | defimpl CBOR.Encoder, for: MapSet do 124 | def encode_into(map_set, acc) do 125 | map_set |> MapSet.to_list() |> CBOR.Encoder.encode_into(acc) 126 | end 127 | end 128 | 129 | # We treat all NaiveDateTimes as UTC, if you need to include TimeZone 130 | # information you should convert your data to a regular DateTime 131 | defimpl CBOR.Encoder, for: NaiveDateTime do 132 | def encode_into(naive_datetime, acc) do 133 | CBOR.Encoder.encode_into( 134 | NaiveDateTime.to_iso8601(naive_datetime) <> "Z", 135 | CBOR.Utils.encode_head(6, 0, acc) 136 | ) 137 | end 138 | end 139 | 140 | # We convert Ranges into lists since there is no 'range' representation 141 | defimpl CBOR.Encoder, for: Range do 142 | def encode_into(range, acc) do 143 | range |> Enum.into([]) |> CBOR.Encoder.encode_into(acc) 144 | end 145 | end 146 | 147 | defimpl CBOR.Encoder, for: Time do 148 | def encode_into(time, acc) do 149 | CBOR.Encoder.encode_into( 150 | Time.to_iso8601(time), 151 | CBOR.Utils.encode_head(6, 0, acc) 152 | ) 153 | end 154 | end 155 | 156 | # We convert all Tuples to Lists since CBOR has no concept of Tuples, 157 | # and they are basically the same thing anyway. This also fixes the problem 158 | # of having to deal with keyword lists so we don't lose any information. 159 | defimpl CBOR.Encoder, for: Tuple do 160 | def encode_into(tuple, acc) do 161 | tuple |> Tuple.to_list() |> CBOR.Encoder.encode_into(acc) 162 | end 163 | end 164 | 165 | defimpl CBOR.Encoder, for: URI do 166 | def encode_into(uri, acc) do 167 | CBOR.Encoder.encode_into( 168 | URI.to_string(uri), 169 | CBOR.Utils.encode_head(6, 32, acc) 170 | ) 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /lib/cbor/decoder.ex: -------------------------------------------------------------------------------- 1 | defmodule CBOR.Decoder do 2 | def decode(binary) do 3 | decode(binary, header(binary)) 4 | end 5 | 6 | def decode(_binary, {mt, :indefinite, rest}) do 7 | case mt do 8 | 2 -> mark_as_bytes(decode_string_indefinite(rest, 2, [])) 9 | 3 -> decode_string_indefinite(rest, 3, []) 10 | 4 -> decode_array_indefinite(rest, []) 11 | 5 -> decode_map_indefinite(rest, %{}) 12 | end 13 | end 14 | 15 | def decode(bin, {mt, value, rest}) do 16 | case mt do 17 | 0 -> {value, rest} 18 | 1 -> {-value - 1, rest} 19 | 2 -> mark_as_bytes(decode_string(rest, value)) 20 | 3 -> decode_string(rest, value) 21 | 4 -> decode_array(value, rest) 22 | 5 -> decode_map(value, rest) 23 | 6 -> decode_other(value, decode(rest)) 24 | 7 -> decode_float(bin, value, rest) 25 | end 26 | end 27 | 28 | defp header(<>) when val < 24 do 29 | {mt, val, rest} 30 | end 31 | 32 | defp header(<>) do 33 | {mt, val, rest} 34 | end 35 | 36 | defp header(<>) do 37 | {mt, val, rest} 38 | end 39 | 40 | defp header(<>) do 41 | {mt, val, rest} 42 | end 43 | 44 | defp header(<>) do 45 | {mt, val, rest} 46 | end 47 | 48 | defp header(<>) do 49 | {mt, :indefinite, rest} 50 | end 51 | 52 | defp decode_string(rest, len) do 53 | <> = rest 54 | {value, new_rest} 55 | end 56 | 57 | defp decode_string_indefinite(rest, actmt, acc) do 58 | case header(rest) do 59 | {7, :indefinite, new_rest} -> 60 | {Enum.join(Enum.reverse(acc)), new_rest} 61 | 62 | {^actmt, len, mid_rest} -> 63 | <> = mid_rest 64 | decode_string_indefinite(new_rest, actmt, [value | acc]) 65 | end 66 | end 67 | 68 | defp decode_array(0, rest), do: {[], rest} 69 | defp decode_array(len, rest), do: decode_array(len, [], rest) 70 | defp decode_array(0, acc, bin), do: {Enum.reverse(acc), bin} 71 | defp decode_array(len, acc, bin) do 72 | {value, bin_rest} = decode(bin) 73 | decode_array(len - 1, [value|acc], bin_rest) 74 | end 75 | 76 | defp decode_array_indefinite(<<0xff, new_rest::binary>>, acc) do 77 | {Enum.reverse(acc), new_rest} 78 | end 79 | 80 | defp decode_array_indefinite(rest, acc) do 81 | {value, new_rest} = decode(rest) 82 | decode_array_indefinite(new_rest, [value | acc]) 83 | end 84 | 85 | defp decode_map(0, rest), do: {%{}, rest} 86 | defp decode_map(len, rest), do: decode_map(len, %{}, rest) 87 | defp decode_map(0, acc, bin), do: {acc, bin} 88 | defp decode_map(len, acc, bin) do 89 | {key, key_rest} = decode(bin) 90 | {value, bin_rest} = decode(key_rest) 91 | 92 | decode_map(len - 1, Map.put(acc, key, value), bin_rest) 93 | end 94 | 95 | defp decode_map_indefinite(<<0xff, new_rest::binary>>, acc), do: {acc, new_rest} 96 | defp decode_map_indefinite(rest, acc) do 97 | {key, key_rest} = decode(rest) 98 | {value, new_rest} = decode(key_rest) 99 | decode_map_indefinite(new_rest, Map.put(acc, key, value)) 100 | end 101 | 102 | defp decode_float(bin, value, rest) do 103 | case bin do 104 | <<0xf4, _::binary>> -> {false, rest} 105 | <<0xf5, _::binary>> -> {true, rest} 106 | <<0xf6, _::binary>> -> {nil, rest} 107 | <<0xf7, _::binary>> -> {:__undefined__, rest} 108 | 109 | <<0xf9, sign::size(1), exp::size(5), mant::size(10), _::binary>> -> 110 | {decode_half(sign, exp, mant), rest} 111 | 112 | <<0xfa, value::float-size(32), _::binary>> -> 113 | {value, rest} 114 | 115 | <<0xfa, sign::size(1), 255::size(8), mant::size(23), _::binary>> -> 116 | {decode_non_finite(sign, mant), rest} 117 | 118 | <<0xfb, value::float, _::binary >> -> 119 | {value, rest} 120 | 121 | <<0xfb, sign::size(1), 2047::size(11), mant::size(52), _::binary>> -> 122 | {decode_non_finite(sign, mant), rest} 123 | 124 | _ -> {%CBOR.Tag{tag: :simple, value: value}, rest} 125 | end 126 | end 127 | 128 | defp decode_other(value, {inner, rest}), do: {decode_tag(value, inner), rest} 129 | 130 | def decode_non_finite(0, 0), do: %CBOR.Tag{tag: :float, value: :inf} 131 | def decode_non_finite(1, 0), do: %CBOR.Tag{tag: :float, value: :"-inf"} 132 | def decode_non_finite(_, _), do: %CBOR.Tag{tag: :float, value: :nan} 133 | 134 | defp decode_half(sign, 31, mant), do: decode_non_finite(sign, mant) 135 | 136 | # 2**112 -- difference in bias 137 | defp decode_half(sign, exp, mant) do 138 | <> = <> 139 | value * 5192296858534827628530496329220096.0 140 | end 141 | 142 | defp decode_tag(0, value), do: decode_datetime(value) 143 | 144 | defp decode_tag(3, value), do: -decode_tag(2, value) - 1 145 | 146 | defp decode_tag(2, value) do 147 | case value do 148 | %CBOR.Tag{tag: :bytes, value: bytes} when is_binary(bytes) -> 149 | size = byte_size(bytes) 150 | <> = bytes 151 | res 152 | 153 | bytes when is_binary(bytes) -> 154 | size = byte_size(bytes) 155 | <> = bytes 156 | res 157 | end 158 | end 159 | 160 | defp decode_tag(32, value), do: URI.parse(value) 161 | defp decode_tag(tag, value), do: %CBOR.Tag{tag: tag, value: value} 162 | 163 | defp mark_as_bytes({x, rest}), do: {%CBOR.Tag{tag: :bytes, value: x}, rest} 164 | 165 | defp decode_datetime(value) do 166 | case DateTime.from_iso8601(value) do 167 | {:ok, datetime, _offset} -> datetime 168 | {:error, _reason} -> decode_date(value) 169 | end 170 | end 171 | 172 | defp decode_date(value) do 173 | case Date.from_iso8601(value) do 174 | {:ok, date} -> date 175 | {:error, _reason} -> decode_time(value) 176 | end 177 | end 178 | 179 | defp decode_time(value) do 180 | case Time.from_iso8601(value) do 181 | {:ok, time} -> time 182 | {:error, _reason} -> %CBOR.Tag{tag: 0, value: value} 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /test/cbor/decoder_test.exs: -------------------------------------------------------------------------------- 1 | defmodule CBOR.DecoderTest do 2 | use ExUnit.Case, async: true 3 | 4 | test "too much data" do 5 | encoded = <<1, 102, 111, 111>> 6 | assert CBOR.decode(encoded) == {:ok, 1, "foo"} 7 | end 8 | 9 | test "too little data" do 10 | assert_raise(FunctionClauseError, fn -> 11 | CBOR.Decoder.decode("") == 1 12 | end) 13 | end 14 | 15 | test "a non-binary value" do 16 | assert CBOR.decode([]) == {:error, :cannot_decode_non_binary_values} 17 | end 18 | 19 | test "RFC 7049 Appendix A Example 1" do 20 | encoded = <<0>> 21 | assert CBOR.decode(encoded) == {:ok, 0, ""} 22 | end 23 | 24 | test "RFC 7049 Appendix A Example 2" do 25 | encoded = <<1>> 26 | assert CBOR.decode(encoded) == {:ok, 1, ""} 27 | end 28 | 29 | test "RFC 7049 Appendix A Example 3" do 30 | encoded = "\n" 31 | assert CBOR.decode(encoded) == {:ok, 10, ""} 32 | end 33 | 34 | test "RFC 7049 Appendix A Example 4" do 35 | encoded = <<23>> 36 | assert CBOR.decode(encoded) == {:ok, 23, ""} 37 | end 38 | 39 | test "RFC 7049 Appendix A Example 5" do 40 | encoded = <<24, 24>> 41 | assert CBOR.decode(encoded) == {:ok, 24, ""} 42 | end 43 | 44 | test "RFC 7049 Appendix A Example 6" do 45 | encoded = <<24, 25>> 46 | assert CBOR.decode(encoded) == {:ok, 25, ""} 47 | end 48 | 49 | test "RFC 7049 Appendix A Example 7" do 50 | encoded = <<24, 100>> 51 | assert CBOR.decode(encoded) == {:ok, 100, ""} 52 | end 53 | 54 | test "RFC 7049 Appendix A Example 8" do 55 | encoded = <<25, 3, 232>> 56 | assert CBOR.decode(encoded) == {:ok, 1000, ""} 57 | end 58 | 59 | test "RFC 7049 Appendix A Example 9" do 60 | encoded = <<26, 0, 15, 66, 64>> 61 | assert CBOR.decode(encoded) == {:ok, 1000000, ""} 62 | end 63 | 64 | test "RFC 7049 Appendix A Example 10" do 65 | encoded = <<27, 0, 0, 0, 232, 212, 165, 16, 0>> 66 | assert CBOR.decode(encoded) == {:ok, 1000000000000, ""} 67 | end 68 | 69 | test "RFC 7049 Appendix A Example 11" do 70 | encoded = <<27, 255, 255, 255, 255, 255, 255, 255, 255>> 71 | assert CBOR.decode(encoded) == {:ok, 18446744073709551615, ""} 72 | end 73 | 74 | test "RFC 7049 Appendix A Example 12" do 75 | encoded = <<194, 73, 1, 0, 0, 0, 0, 0, 0, 0, 0>> 76 | assert CBOR.decode(encoded) == {:ok, 18446744073709551616, ""} 77 | end 78 | 79 | test "RFC 7049 Appendix A Example 13" do 80 | encoded = <<59, 255, 255, 255, 255, 255, 255, 255, 255>> 81 | assert CBOR.decode(encoded) == {:ok, -18446744073709551616, ""} 82 | end 83 | 84 | test "RFC 7049 Appendix A Example 14" do 85 | encoded = <<195, 73, 1, 0, 0, 0, 0, 0, 0, 0, 0>> 86 | assert CBOR.decode(encoded) == {:ok, -18446744073709551617, ""} 87 | end 88 | 89 | test "RFC 7049 Appendix A Example 15" do 90 | encoded = " " 91 | assert CBOR.decode(encoded) == {:ok, -1, ""} 92 | end 93 | 94 | test "RFC 7049 Appendix A Example 16" do 95 | encoded = ")" 96 | assert CBOR.decode(encoded) == {:ok, -10, ""} 97 | end 98 | 99 | test "RFC 7049 Appendix A Example 17" do 100 | encoded = "8c" 101 | assert CBOR.decode(encoded) == {:ok, -100, ""} 102 | end 103 | 104 | test "RFC 7049 Appendix A Example 18" do 105 | encoded = <<57, 3, 231>> 106 | assert CBOR.decode(encoded) == {:ok, -1000, ""} 107 | end 108 | 109 | test "RFC 7049 Appendix A Example 19" do 110 | encoded = <<249, 0, 0>> 111 | assert CBOR.decode(encoded) == {:ok, 0.0, ""} 112 | end 113 | 114 | test "RFC 7049 Appendix A Example 20" do 115 | encoded = <<249, 128, 0>> 116 | assert CBOR.decode(encoded) == {:ok, 0.0, ""} 117 | end 118 | 119 | test "RFC 7049 Appendix A Example 21" do 120 | encoded = <<249, 60, 0>> 121 | assert CBOR.decode(encoded) == {:ok, 1.0, ""} 122 | end 123 | 124 | test "RFC 7049 Appendix A Example 22" do 125 | encoded = <<251, 63, 241, 153, 153, 153, 153, 153, 154>> 126 | assert CBOR.decode(encoded) == {:ok, 1.1, ""} 127 | end 128 | 129 | test "RFC 7049 Appendix A Example 23" do 130 | encoded = <<249, 62, 0>> 131 | assert CBOR.decode(encoded) == {:ok, 1.5, ""} 132 | end 133 | 134 | test "RFC 7049 Appendix A Example 24" do 135 | encoded = <<249, 123, 255>> 136 | assert CBOR.decode(encoded) == {:ok, 65504.0, ""} 137 | end 138 | 139 | test "RFC 7049 Appendix A Example 25" do 140 | encoded = <<250, 71, 195, 80, 0>> 141 | assert CBOR.decode(encoded) == {:ok, 1.0e5, ""} 142 | end 143 | 144 | test "RFC 7049 Appendix A Example 26" do 145 | encoded = <<250, 127, 127, 255, 255>> 146 | assert CBOR.decode(encoded) == {:ok, 3.4028234663852886e38, ""} 147 | end 148 | 149 | test "RFC 7049 Appendix A Example 27" do 150 | encoded = <<251, 126, 55, 228, 60, 136, 0, 117, 156>> 151 | assert CBOR.decode(encoded) == {:ok, 1.0e300, ""} 152 | end 153 | 154 | test "RFC 7049 Appendix A Example 28" do 155 | encoded = <<249, 0, 1>> 156 | assert CBOR.decode(encoded) == {:ok, 5.960464477539063e-8, ""} 157 | end 158 | 159 | test "RFC 7049 Appendix A Example 29" do 160 | encoded = <<249, 4, 0>> 161 | assert CBOR.decode(encoded) == {:ok, 6.103515625e-5, ""} 162 | end 163 | 164 | test "RFC 7049 Appendix A Example 30" do 165 | encoded = <<249, 196, 0>> 166 | assert CBOR.decode(encoded) == {:ok, -4.0, ""} 167 | end 168 | 169 | test "RFC 7049 Appendix A Example 31" do 170 | encoded = <<251, 192, 16, 102, 102, 102, 102, 102, 102>> 171 | assert CBOR.decode(encoded) == {:ok, -4.1, ""} 172 | end 173 | 174 | test "RFC 7049 Appendix A Example 32" do 175 | encoded = <<249, 124, 0>> 176 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :inf}, ""} 177 | end 178 | 179 | test "RFC 7049 Appendix A Example 33" do 180 | encoded = <<249, 126, 0>> 181 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :nan}, ""} 182 | end 183 | 184 | test "RFC 7049 Appendix A Example 34" do 185 | encoded = <<249, 252, 0>> 186 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :"-inf"}, ""} 187 | end 188 | 189 | test "RFC 7049 Appendix A Example 35" do 190 | encoded = <<250, 127, 128, 0, 0>> 191 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :inf}, ""} 192 | end 193 | 194 | test "RFC 7049 Appendix A Example 36" do 195 | encoded = <<250, 127, 192, 0, 0>> 196 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :nan}, ""} 197 | end 198 | 199 | test "RFC 7049 Appendix A Example 37" do 200 | encoded = <<250, 255, 128, 0, 0>> 201 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :"-inf"}, ""} 202 | end 203 | 204 | test "RFC 7049 Appendix A Example 38" do 205 | encoded = <<251, 127, 240, 0, 0, 0, 0, 0, 0>> 206 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :inf}, ""} 207 | end 208 | 209 | test "RFC 7049 Appendix A Example 39" do 210 | encoded = <<251, 127, 248, 0, 0, 0, 0, 0, 0>> 211 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :nan}, ""} 212 | end 213 | 214 | test "RFC 7049 Appendix A Example 40" do 215 | encoded = <<251, 255, 240, 0, 0, 0, 0, 0, 0>> 216 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :float, value: :"-inf"}, ""} 217 | end 218 | 219 | test "RFC 7049 Appendix A Example 41" do 220 | encoded = <<244>> 221 | assert CBOR.decode(encoded) == {:ok, false, ""} 222 | end 223 | 224 | test "RFC 7049 Appendix A Example 42" do 225 | encoded = <<245>> 226 | assert CBOR.decode(encoded) == {:ok, true, ""} 227 | end 228 | 229 | test "RFC 7049 Appendix A Example 43" do 230 | encoded = <<246>> 231 | assert CBOR.decode(encoded) == {:ok, nil, ""} 232 | end 233 | 234 | test "RFC 7049 Appendix A Example 44" do 235 | encoded = <<247>> 236 | assert CBOR.decode(encoded) == {:ok, :__undefined__, ""} 237 | end 238 | 239 | test "RFC 7049 Appendix A Example 45" do 240 | encoded = <<240>> 241 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :simple, value: 16}, ""} 242 | end 243 | 244 | test "RFC 7049 Appendix A Example 46" do 245 | encoded = <<248, 24>> 246 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :simple, value: 24}, ""} 247 | end 248 | 249 | test "RFC 7049 Appendix A Example 47" do 250 | encoded = <<248, 255>> 251 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :simple, value: 255}, ""} 252 | end 253 | 254 | test "RFC 7049 Appendix A Example 48" do 255 | encoded = <<192, 116, 50, 48, 49, 51, 45, 48, 51, 45, 50, 49, 84, 50, 48, 58, 48, 52, 58, 48, 48, 90>> 256 | assert CBOR.decode(encoded) == {:ok, ~U[2013-03-21 20:04:00Z], ""} 257 | end 258 | 259 | test "RFC 7049 Appendix A Example 49" do 260 | encoded = <<193, 26, 81, 75, 103, 176>> 261 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: 1, value: 1363896240}, ""} 262 | end 263 | 264 | test "RFC 7049 Appendix A Example 50" do 265 | encoded = <<193, 251, 65, 212, 82, 217, 236, 32, 0, 0>> 266 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: 1, value: 1363896240.5}, ""} 267 | end 268 | 269 | test "RFC 7049 Appendix A Example 51" do 270 | encoded = <<215, 68, 1, 2, 3, 4>> 271 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: 23, value: %CBOR.Tag{tag: :bytes, value: <<1, 2, 3, 4>>}}, ""} 272 | end 273 | 274 | test "RFC 7049 Appendix A Example 52" do 275 | encoded = <<216, 24, 69, 100, 73, 69, 84, 70>> 276 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: 24, value: %CBOR.Tag{tag: :bytes, value: "dIETF"}}, ""} 277 | end 278 | 279 | test "RFC 7049 Appendix A Example 53" do 280 | encoded = <<216, 32, 118, 104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 101, 120, 97, 109, 112, 108, 101, 46, 99, 111, 109>> 281 | assert CBOR.decode(encoded) == {:ok, %URI{ 282 | authority: "www.example.com", 283 | fragment: nil, 284 | host: "www.example.com", 285 | path: nil, 286 | port: 80, 287 | query: nil, 288 | scheme: "http", 289 | userinfo: nil 290 | }, ""} 291 | end 292 | 293 | test "RFC 7049 Appendix A Example 54" do 294 | encoded = "@" 295 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :bytes, value: ""}, ""} 296 | end 297 | 298 | test "RFC 7049 Appendix A Example 55" do 299 | encoded = <<68, 1, 2, 3, 4>> 300 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :bytes, value: <<1, 2, 3, 4>>}, ""} 301 | end 302 | 303 | test "RFC 7049 Appendix A Example 56" do 304 | assert CBOR.decode("`") == {:ok, "", ""} 305 | end 306 | 307 | test "RFC 7049 Appendix A Example 57" do 308 | assert CBOR.decode("aa") == {:ok, "a", ""} 309 | end 310 | 311 | test "RFC 7049 Appendix A Example 58" do 312 | assert CBOR.decode("dIETF") == {:ok, "IETF", ""} 313 | end 314 | 315 | test "RFC 7049 Appendix A Example 59" do 316 | assert CBOR.decode("b\"\\") == {:ok, "\"\\", ""} 317 | end 318 | 319 | test "RFC 7049 Appendix A Example 60" do 320 | assert CBOR.decode("bü") == {:ok, "ü", ""} 321 | end 322 | 323 | test "RFC 7049 Appendix A Example 61" do 324 | assert CBOR.decode("c水") == {:ok, "水", ""} 325 | end 326 | 327 | test "RFC 7049 Appendix A Example 62" do 328 | assert CBOR.decode("d𐅑") == {:ok, "𐅑", ""} 329 | end 330 | 331 | test "RFC 7049 Appendix A Example 63" do 332 | assert CBOR.decode(<<128>>) == {:ok, [], ""} 333 | end 334 | 335 | test "RFC 7049 Appendix A Example 64" do 336 | encoded = <<131, 1, 2, 3>> 337 | assert CBOR.decode(encoded) == {:ok, [1, 2, 3], ""} 338 | end 339 | 340 | test "RFC 7049 Appendix A Example 65" do 341 | encoded = <<131, 1, 130, 2, 3, 130, 4, 5>> 342 | assert CBOR.decode(encoded) == {:ok, [1, [2, 3], [4, 5]], ""} 343 | end 344 | 345 | test "RFC 7049 Appendix A Example 66" do 346 | encoded = <<152, 25, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25>> 347 | assert CBOR.decode(encoded) == {:ok, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], ""} 348 | end 349 | 350 | test "RFC 7049 Appendix A Example 67" do 351 | encoded = <<160>> 352 | assert CBOR.decode(encoded) == {:ok, %{}, ""} 353 | end 354 | 355 | test "RFC 7049 Appendix A Example 68" do 356 | encoded = <<162, 1, 2, 3, 4>> 357 | assert CBOR.decode(encoded) == {:ok, %{1 => 2, 3 => 4}, ""} 358 | end 359 | 360 | test "RFC 7049 Appendix A Example 69" do 361 | encoded = <<162, 97, 97, 1, 97, 98, 130, 2, 3>> 362 | assert CBOR.decode(encoded) == {:ok, %{"a" => 1, "b" => [2, 3]}, ""} 363 | end 364 | 365 | test "RFC 7049 Appendix A Example 70" do 366 | encoded = <<130, 97, 97, 161, 97, 98, 97, 99>> 367 | assert CBOR.decode(encoded) == {:ok, ["a", %{"b" => "c"}], ""} 368 | end 369 | 370 | test "RFC 7049 Appendix A Example 71" do 371 | encoded = <<165, 97, 97, 97, 65, 97, 98, 97, 66, 97, 99, 97, 67, 97, 100, 97, 68, 97, 101, 97, 69>> 372 | assert CBOR.decode(encoded) == {:ok, %{"a" => "A", "b" => "B", "c" => "C", "d" => "D", "e" => "E"}, ""} 373 | end 374 | 375 | test "RFC 7049 Appendix A Example 72" do 376 | encoded = <<95, 66, 1, 2, 67, 3, 4, 5, 255>> 377 | assert CBOR.decode(encoded) == {:ok, %CBOR.Tag{tag: :bytes, value: <<1, 2, 3, 4, 5>>}, ""} 378 | end 379 | 380 | test "RFC 7049 Appendix A Example 73" do 381 | encoded = <<127, 101, 115, 116, 114, 101, 97, 100, 109, 105, 110, 103, 255>> 382 | assert CBOR.decode(encoded) == {:ok, "streaming", ""} 383 | end 384 | 385 | test "RFC 7049 Appendix A Example 74" do 386 | encoded = <<159, 255>> 387 | assert CBOR.decode(encoded) == {:ok, [], ""} 388 | end 389 | 390 | test "RFC 7049 Appendix A Example 75" do 391 | encoded = <<159, 1, 130, 2, 3, 159, 4, 5, 255, 255>> 392 | assert CBOR.decode(encoded) == {:ok, [1, [2, 3], [4, 5]], ""} 393 | end 394 | 395 | test "RFC 7049 Appendix A Example 76" do 396 | encoded = <<159, 1, 130, 2, 3, 130, 4, 5, 255>> 397 | assert CBOR.decode(encoded) == {:ok, [1, [2, 3], [4, 5]], ""} 398 | end 399 | 400 | test "RFC 7049 Appendix A Example 77" do 401 | encoded = <<131, 1, 130, 2, 3, 159, 4, 5, 255>> 402 | assert CBOR.decode(encoded) == {:ok, [1, [2, 3], [4, 5]], ""} 403 | end 404 | 405 | test "RFC 7049 Appendix A Example 78" do 406 | encoded = <<131, 1, 159, 2, 3, 255, 130, 4, 5>> 407 | assert CBOR.decode(encoded) == {:ok, [1, [2, 3], [4, 5]], ""} 408 | end 409 | 410 | test "RFC 7049 Appendix A Example 79" do 411 | encoded = <<159, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 24, 24, 25, 255>> 412 | assert CBOR.decode(encoded) == {:ok, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], ""} 413 | end 414 | 415 | test "RFC 7049 Appendix A Example 80" do 416 | encoded = <<191, 97, 97, 1, 97, 98, 159, 2, 3, 255, 255>> 417 | assert CBOR.decode(encoded) == {:ok, %{"a" => 1, "b" => [2, 3]}, ""} 418 | end 419 | 420 | test "RFC 7049 Appendix A Example 81" do 421 | encoded = <<130, 97, 97, 191, 97, 98, 97, 99, 255>> 422 | assert CBOR.decode(encoded) == {:ok, ["a", %{"b" => "c"}], ""} 423 | end 424 | 425 | test "RFC 7049 Appendix A Example 82" do 426 | encoded = <<191, 99, 70, 117, 110, 245, 99, 65, 109, 116, 33, 255>> 427 | assert CBOR.decode(encoded) == {:ok, %{"Fun" => true, "Amt" => -2}, ""} 428 | end 429 | 430 | test "receiving a MatchError" do 431 | encoded = "You done goofed" 432 | assert CBOR.decode(encoded) == {:error, :cbor_match_error} 433 | end 434 | end 435 | --------------------------------------------------------------------------------