├── .gitignore ├── .travis.yml ├── README.md ├── lib ├── message_pack.ex └── message_pack │ ├── ext.ex │ ├── packer.ex │ └── unpacker.ex ├── mix.exs └── test ├── cases.json ├── cases.msg ├── message_pack_cases_test.exs ├── message_pack_ext_test.exs ├── message_pack_proper_test.exs ├── message_pack_test.exs └── test_helper.exs /.gitignore: -------------------------------------------------------------------------------- 1 | /_build 2 | /deps 3 | erl_crash.dump 4 | *.ez 5 | mix.lock 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: elixir 2 | sudo: false 3 | notifications: 4 | recipients: 5 | - yuki@gnnk.net 6 | elixir: 7 | - 1.0.0 8 | - 1.0.1 9 | - 1.0.2 10 | - 1.0.3 11 | - 1.0.4 12 | otp_release: 13 | - 17.4 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MessagePack for Elixir 2 | 3 | [![Build Status](https://travis-ci.org/mururu/msgpack-elixir.png?branch=master)](https://travis-ci.org/mururu/msgpack-elixir) 4 | 5 | ## Installation 6 | 7 | Add `:message_pack` as a dependency in your `mix.exs` file. 8 | 9 | ```elixir 10 | defp deps do 11 | [{:message_pack, "~> 0.2.0"}] 12 | end 13 | ``` 14 | 15 | ## Usage 16 | 17 | ```elixir 18 | # pack 19 | MessagePack.pack([1,2,3]) #=> { :ok, <<147,1,2,3>> } 20 | MessagePack.pack!([1,2,3]) #=> <<147,1,2,3>> 21 | 22 | # unpack 23 | MessagePack.unpack(<<147,1,2,3>>) #=> { :ok, [1,2,3] } 24 | MessagePack.unpack!(<<147,1,2,3>>) #=> [1,2,3] 25 | 26 | # unpack_once 27 | MessagePack.unpack_once(<<147,1,2,3,4>>) #=> {:ok, {[1, 2, 3], <<4>>}} 28 | MessagePack.unpack_once!(<<147,1,2,3,4>>) #=> {[1, 2, 3], <<4>>} 29 | ``` 30 | 31 | ## Options 32 | 33 | * `enable_string` 34 | 35 | Support string type. This options is `false` by default. 36 | 37 | ```elixir 38 | iex(1)> { :ok, bin } = MessagePack.pack(<<255>>) 39 | {:ok, <<161, 255>>} 40 | iex(3)> MessagePack.unpack(<<161, 255>>) 41 | {:ok, <<255>>} 42 | iex(4)> MessagePack.unpack(<<161, 255>>, enable_string: true) 43 | {:error, {:invalid_string, <<255>>}} 44 | ``` 45 | 46 | * `ext` 47 | 48 | Support extention type. 49 | 50 | See `test/message_pack_ext_test.exs`. 51 | 52 | ## License 53 | 54 | MIT 55 | -------------------------------------------------------------------------------- /lib/message_pack.ex: -------------------------------------------------------------------------------- 1 | defmodule MessagePack do 2 | defdelegate pack(term), to: MessagePack.Packer 3 | defdelegate pack(term, options), to: MessagePack.Packer 4 | defdelegate pack!(term), to: MessagePack.Packer 5 | defdelegate pack!(term, options), to: MessagePack.Packer 6 | 7 | defdelegate unpack(term), to: MessagePack.Unpacker 8 | defdelegate unpack(term, options), to: MessagePack.Unpacker 9 | defdelegate unpack!(term), to: MessagePack.Unpacker 10 | defdelegate unpack!(term, options), to: MessagePack.Unpacker 11 | 12 | defdelegate unpack_once(term), to: MessagePack.Unpacker 13 | defdelegate unpack_once(term, options), to: MessagePack.Unpacker 14 | defdelegate unpack_once!(term), to: MessagePack.Unpacker 15 | defdelegate unpack_once!(term, options), to: MessagePack.Unpacker 16 | end 17 | -------------------------------------------------------------------------------- /lib/message_pack/ext.ex: -------------------------------------------------------------------------------- 1 | defmodule MessagePack.Ext do 2 | 3 | use Behaviour 4 | 5 | @type type :: non_neg_integer 6 | 7 | defcallback pack(term) :: { :ok, { type, binary } } | { :error, term } 8 | defcallback unpack(type, binary) :: { :ok, term } | { :error, term } 9 | 10 | defmodule Behaviour do 11 | defmacro __using__(_) do 12 | quote do 13 | @behaviour MessagePack.Ext 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/message_pack/packer.ex: -------------------------------------------------------------------------------- 1 | defmodule MessagePack.Packer do 2 | @spec pack(term) :: { :ok, binary } | { :error, term } 3 | @spec pack(term, Keyword.t) :: { :ok, binary } | { :error, term } 4 | def pack(term, options \\ []) do 5 | options = parse_options(options) 6 | 7 | case do_pack(term, options) do 8 | { :error, _ } = error -> 9 | error 10 | packed -> 11 | { :ok, packed } 12 | end 13 | end 14 | 15 | @spec pack!(term) :: binary | no_return 16 | @spec pack!(term, Keyword.t) :: binary | no_return 17 | def pack!(term, options \\ []) do 18 | case pack(term, options) do 19 | { :ok, packed } -> 20 | packed 21 | { :error, error } -> 22 | raise ArgumentError, message: inspect(error) 23 | end 24 | end 25 | 26 | defp parse_options(options) do 27 | enable_string = !!options[:enable_string] 28 | 29 | {packer, unpacker} = case options[:ext] do 30 | nil -> 31 | { nil, nil } 32 | mod when is_atom(mod) -> 33 | { &mod.pack/1, &mod.unpack/2 } 34 | list when is_list(list) -> 35 | { list[:packer], list[:unpacker] } 36 | end 37 | 38 | %{enable_string: enable_string, ext_packer: packer, ext_unpacker: unpacker} 39 | end 40 | 41 | defp do_pack(nil, _), do: << 0xC0 :: size(8) >> 42 | defp do_pack(false, _), do: << 0xC2 :: size(8) >> 43 | defp do_pack(true, _), do: << 0xC3 :: size(8) >> 44 | defp do_pack(atom, options) when is_atom(atom), do: do_pack(Atom.to_string(atom), options) 45 | defp do_pack(i, _) when is_integer(i) and i < 0, do: pack_int(i) 46 | defp do_pack(i, _) when is_integer(i), do: pack_uint(i) 47 | defp do_pack(f, _) when is_float(f), do: << 0xCB :: size(8), f :: size(64)-big-float-unit(1)>> 48 | defp do_pack(binary, %{enable_string: true}) when is_binary(binary) do 49 | if String.valid?(binary) do 50 | pack_string(binary) 51 | else 52 | pack_bin(binary) 53 | end 54 | end 55 | defp do_pack(binary, _) when is_binary(binary), do: pack_raw(binary) 56 | defp do_pack(list, options) when is_list(list), do: pack_array(list, options) 57 | defp do_pack(%{__struct__: _}=term, %{ext_packer: packer}) when is_function(packer), do: pack_ext_wrap(term, packer) 58 | defp do_pack(map, options) when is_map(map), do: pack_map(Enum.into(map,[]), options) 59 | 60 | defp do_pack(term, %{ext_packer: packer}) when is_function(packer), do: pack_ext_wrap(term, packer) 61 | defp do_pack(term, _), do: { :error, { :badarg, term } } 62 | 63 | defp pack_ext_wrap(term, packer) do 64 | case pack_ext(term, packer) do 65 | { :ok, packed } -> packed 66 | { :error, _ } = error -> error 67 | end 68 | end 69 | 70 | defp pack_int(i) when i >= -32, do: << 0b111 :: 3, i :: 5 >> 71 | defp pack_int(i) when i >= -128, do: << 0xD0 :: 8, i :: 8-big-signed-integer-unit(1) >> 72 | defp pack_int(i) when i >= -0x8000, do: << 0xD1 :: 8, i :: 16-big-signed-integer-unit(1) >> 73 | defp pack_int(i) when i >= -0x80000000, do: << 0xD2 :: 8, i :: 32-big-signed-integer-unit(1) >> 74 | defp pack_int(i) when i >= -0x8000000000000000 , do: << 0xD3 :: 8, i :: 64-big-signed-integer-unit(1) >> 75 | defp pack_int(i), do: { :error, { :too_big, i } } 76 | 77 | defp pack_uint(i) when i < 0x80, do: << 0 :: 1, i :: 7 >> 78 | defp pack_uint(i) when i < 0x100, do: << 0xCC :: 8, i :: 8 >> 79 | defp pack_uint(i) when i < 0x10000, do: << 0xCD :: 8, i :: 16-big-unsigned-integer-unit(1) >> 80 | defp pack_uint(i) when i < 0x100000000, do: << 0xCE :: 8, i :: 32-big-unsigned-integer-unit(1) >> 81 | defp pack_uint(i) when i < 0x10000000000000000, do: << 0xCF :: 8, i :: 64-big-unsigned-integer-unit(1) >> 82 | defp pack_uint(i), do: { :error, { :too_big, i } } 83 | 84 | # for old row format 85 | defp pack_raw(binary) when byte_size(binary) < 32 do 86 | << 0b101 :: 3, byte_size(binary) :: 5, binary :: binary >> 87 | end 88 | defp pack_raw(binary) when byte_size(binary) < 0x10000 do 89 | << 0xDA :: 8, byte_size(binary) :: 16-big-unsigned-integer-unit(1), binary :: binary >> 90 | end 91 | defp pack_raw(binary) when byte_size(binary) < 0x100000000 do 92 | << 0xDB :: 8, byte_size(binary) :: 32-big-unsigned-integer-unit(1), binary :: binary >> 93 | end 94 | defp pack_raw(binary), do: { :error, { :too_big, binary } } 95 | 96 | # for string format 97 | defp pack_string(binary) when byte_size(binary) < 32 do 98 | << 0b101 :: 3, byte_size(binary) :: 5, binary :: binary >> 99 | end 100 | defp pack_string(binary) when byte_size(binary) < 0x100 do 101 | << 0xD9 :: 8, byte_size(binary) :: 8-big-unsigned-integer-unit(1), binary :: binary >> 102 | end 103 | defp pack_string(binary) when byte_size(binary) < 0x10000 do 104 | << 0xDA :: 8, byte_size(binary) :: 16-big-unsigned-integer-unit(1), binary :: binary >> 105 | end 106 | defp pack_string(binary) when byte_size(binary) < 0x100000000 do 107 | << 0xDB :: 8, byte_size(binary) :: 32-big-unsigned-integer-unit(1), binary :: binary >> 108 | end 109 | defp pack_string(binary), do: { :error, { :too_big, binary } } 110 | 111 | # for binary format 112 | defp pack_bin(binary) when byte_size(binary) < 0x100 do 113 | << 0xC4 :: 8, byte_size(binary) :: 8-big-unsigned-integer-unit(1), binary :: binary >> 114 | end 115 | defp pack_bin(binary) when byte_size(binary) < 0x10000 do 116 | << 0xC5 :: 8, byte_size(binary) :: 16-big-unsigned-integer-unit(1), binary :: binary >> 117 | end 118 | defp pack_bin(binary) when byte_size(binary) < 0x100000000 do 119 | << 0xC6 :: 8, byte_size(binary) :: 32-big-unsigned-integer-unit(1), binary :: binary >> 120 | end 121 | defp pack_bin(binary) do 122 | { :error, { :too_big, binary } } 123 | end 124 | 125 | defp pack_map(map, options) do 126 | case do_pack_map(map, options) do 127 | { :ok, binary } -> 128 | case length(map) do 129 | len when len < 16 -> 130 | << 0b1000 :: 4, len :: 4-integer-unit(1), binary :: binary >> 131 | len when len < 0x10000 -> 132 | << 0xDE :: 8, len :: 16-big-unsigned-integer-unit(1), binary :: binary>> 133 | len when len < 0x100000000 -> 134 | << 0xDF :: 8, len :: 32-big-unsigned-integer-unit(1), binary :: binary>> 135 | _ -> 136 | { :error, { :too_big, map } } 137 | end 138 | error -> 139 | error 140 | end 141 | end 142 | 143 | defp pack_array(list, options) do 144 | case do_pack_array(list, options) do 145 | { :ok, binary } -> 146 | case length(list) do 147 | len when len < 16 -> 148 | << 0b1001 :: 4, len :: 4-integer-unit(1), binary :: binary >> 149 | len when len < 0x10000 -> 150 | << 0xDC :: 8, len :: 16-big-unsigned-integer-unit(1), binary :: binary >> 151 | len when len < 0x100000000 -> 152 | << 0xDD :: 8, len :: 32-big-unsigned-integer-unit(1), binary :: binary >> 153 | _ -> 154 | { :error, { :too_big, list } } 155 | end 156 | error -> 157 | error 158 | end 159 | end 160 | 161 | def do_pack_map(map, options) do 162 | do_pack_map(:lists.reverse(map), <<>>, options) 163 | end 164 | 165 | defp do_pack_map([], acc, _), do: { :ok, acc } 166 | defp do_pack_map([{ k, v }|t], acc, options) do 167 | case do_pack(k, options) do 168 | { :error, _ } = error -> 169 | error 170 | k -> 171 | case do_pack(v, options) do 172 | { :error, _ } = error -> 173 | error 174 | v -> 175 | do_pack_map(t, << k :: binary, v :: binary, acc :: binary >>, options) 176 | end 177 | end 178 | end 179 | 180 | defp do_pack_array(list, options) do 181 | do_pack_array(:lists.reverse(list), <<>>, options) 182 | end 183 | 184 | defp do_pack_array([], acc, _), do: { :ok, acc } 185 | defp do_pack_array([h|t], acc, options) do 186 | case do_pack(h, options) do 187 | { :error, _ } = error -> 188 | error 189 | binary -> 190 | do_pack_array(t, << binary :: binary, acc :: binary >>, options) 191 | end 192 | end 193 | 194 | defp pack_ext(term, packer) do 195 | case packer.(term) do 196 | { :ok, { type, data } } when type < 0x100 and is_binary(data) -> 197 | maybe_bin = case byte_size(data) do 198 | 1 -> << 0xD4, type :: 8, data :: binary >> 199 | 2 -> << 0xD5, type :: 8, data :: binary >> 200 | 4 -> << 0xD6, type :: 8, data :: binary >> 201 | 8 -> << 0xD7, type :: 8, data :: binary >> 202 | 16 -> << 0xD8, type :: 8, data :: binary >> 203 | size when size < 0x100 -> 204 | << 0xC7, size :: 8, type :: 8, data :: binary >> 205 | size when size < 0x10000 -> 206 | << 0xC8, size :: 16, type :: 8, data :: binary >> 207 | size when size < 0x100000000 -> 208 | << 0xC9, size :: 32, type :: 8, data :: binary >> 209 | _ -> 210 | { :error, { :too_big, data } } 211 | end 212 | case maybe_bin do 213 | { :error, _ } = error -> 214 | error 215 | bin -> 216 | { :ok, bin } 217 | end 218 | { :ok, other } -> 219 | { :error, { :invalid_ext_data, other } } 220 | { :error, _ } = error -> 221 | error 222 | end 223 | end 224 | end 225 | -------------------------------------------------------------------------------- /lib/message_pack/unpacker.ex: -------------------------------------------------------------------------------- 1 | defmodule MessagePack.Unpacker do 2 | @spec unpack(binary) :: { :ok, term } | { :error, term } 3 | @spec unpack(binary, Keyword.t) :: { :ok, term } | { :error, term } 4 | def unpack(binary, options \\ []) when is_binary(binary) do 5 | options = parse_options(options) 6 | 7 | case do_unpack(binary, options) do 8 | { :error, _ } = error -> 9 | error 10 | { result, "" } -> 11 | { :ok, result } 12 | { _, bin } when is_binary(bin) -> 13 | { :error, :not_just_binary } 14 | end 15 | end 16 | 17 | @spec unpack!(binary) :: term | no_return 18 | @spec unpack!(binary, Keyword.t) :: term | no_return 19 | def unpack!(binary, options \\ []) when is_binary(binary) do 20 | case unpack(binary, options) do 21 | { :ok, result } -> 22 | result 23 | { :error, error } -> 24 | raise ArgumentError, message: inspect(error) 25 | end 26 | end 27 | 28 | @spec unpack(binary) :: { :ok, { term, binary } } | { :error, term } 29 | @spec unpack(binary, Keyword.t) :: { :ok, { term, binary } } | { :error, term } 30 | def unpack_once(binary, options \\ []) when is_binary(binary) do 31 | options = parse_options(options) 32 | 33 | case do_unpack(binary, options) do 34 | { :error, _ } = error -> 35 | error 36 | result -> 37 | { :ok, result } 38 | end 39 | end 40 | 41 | @spec unpack!(binary) :: { term, binary } | no_return 42 | @spec unpack!(binary, Keyword.t) :: { term, binary } | no_return 43 | def unpack_once!(binary, options \\ []) when is_binary(binary) do 44 | case unpack_once(binary, options) do 45 | { :ok, result } -> 46 | result 47 | { :error, error } -> 48 | raise ArgumentError, message: inspect(error) 49 | end 50 | end 51 | 52 | defp parse_options(options) do 53 | enable_string = !!options[:enable_string] 54 | 55 | { packer, unpacker } = case options[:ext] do 56 | nil -> 57 | { nil, nil } 58 | mod when is_atom(mod) -> 59 | { &mod.pack/1, &mod.unpack/2 } 60 | list when is_list(list) -> 61 | { list[:packer], list[:unpacker] } 62 | end 63 | 64 | %{enable_string: enable_string, ext_packer: packer, ext_unpacker: unpacker} 65 | end 66 | 67 | # positive fixnum 68 | defp do_unpack(<< 0 :: 1, v :: 7, rest :: binary >>, _), do: { v, rest } 69 | 70 | # negative fixnum 71 | defp do_unpack(<< 0b111 :: 3, v :: 5, rest :: binary >>, _), do: { v - 0b100000, rest } 72 | 73 | # uint 74 | defp do_unpack(<< 0xCC, uint :: 8-unsigned-integer, rest :: binary >>, _), do: { uint, rest } 75 | defp do_unpack(<< 0xCD, uint :: 16-big-unsigned-integer-unit(1), rest :: binary >>, _), do: { uint, rest } 76 | defp do_unpack(<< 0xCE, uint :: 32-big-unsigned-integer-unit(1), rest :: binary >>, _), do: { uint, rest } 77 | defp do_unpack(<< 0xCF, uint :: 64-big-unsigned-integer-unit(1), rest :: binary >>, _), do: { uint, rest } 78 | 79 | # int 80 | defp do_unpack(<< 0xD0, int :: 8-signed-integer, rest :: binary >>, _), do: { int, rest } 81 | defp do_unpack(<< 0xD1, int :: 16-big-signed-integer-unit(1), rest :: binary >>, _), do: { int, rest } 82 | defp do_unpack(<< 0xD2, int :: 32-big-signed-integer-unit(1), rest :: binary >>, _), do: { int, rest } 83 | defp do_unpack(<< 0xD3, int :: 64-big-signed-integer-unit(1), rest :: binary >>, _), do: { int, rest } 84 | 85 | # nil 86 | defp do_unpack(<< 0xC0, rest :: binary >>, _), do: { nil, rest } 87 | 88 | # boolean 89 | defp do_unpack(<< 0xC2, rest :: binary >>, _), do: { false, rest } 90 | defp do_unpack(<< 0xC3, rest :: binary >>, _), do: { true, rest } 91 | 92 | # float 93 | defp do_unpack(<< 0xCA, float :: 32-float-unit(1), rest :: binary >>, _), do: { float, rest } 94 | defp do_unpack(<< 0xCB, float :: 64-float-unit(1), rest :: binary >>, _), do: { float, rest } 95 | 96 | # old row format 97 | defp do_unpack(<< 0b101 :: 3, len :: 5, binary :: size(len)-binary, rest :: binary >>, %{enable_string: false}), do: { binary, rest } 98 | defp do_unpack(<< 0xDA, len :: 16-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, %{enable_string: false}), do: { binary, rest } 99 | defp do_unpack(<< 0xDB, len :: 32-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, %{enable_string: false}), do: { binary, rest } 100 | 101 | # string 102 | defp do_unpack(<< 0b101 :: 3, len :: 5, binary :: size(len)-binary, rest :: binary >>, %{enable_string: true}) do 103 | if String.valid?(binary) do 104 | { binary, rest } 105 | else 106 | { :error, { :invalid_string, binary } } 107 | end 108 | end 109 | defp do_unpack(<< 0xD9, len :: 8-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, %{enable_string: true}) do 110 | if String.valid?(binary) do 111 | { binary, rest } 112 | else 113 | { :error, { :invalid_string, binary } } 114 | end 115 | end 116 | defp do_unpack(<< 0xDA, len :: 16-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, %{enable_string: true}) do 117 | if String.valid?(binary) do 118 | { binary, rest } 119 | else 120 | { :error, { :invalid_string, binary } } 121 | end 122 | end 123 | defp do_unpack(<< 0xDB, len :: 32-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, %{enable_string: true}) do 124 | if String.valid?(binary) do 125 | { binary, rest } 126 | else 127 | { :error, { :invalid_string, binary } } 128 | end 129 | end 130 | 131 | # binary 132 | defp do_unpack(<< 0xC4, len :: 8-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, _), do: { binary, rest } 133 | defp do_unpack(<< 0xC5, len :: 16-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, _), do: { binary, rest } 134 | defp do_unpack(<< 0xC6, len :: 32-unsigned-integer-unit(1), binary :: size(len)-binary, rest :: binary >>, _), do: { binary, rest } 135 | 136 | # array 137 | defp do_unpack(<< 0b1001 :: 4, len :: 4, rest :: binary >>, options), do: unpack_array(rest, len, options) 138 | defp do_unpack(<< 0xDC, len :: 16-big-unsigned-integer-unit(1), rest :: binary >>, options), do: unpack_array(rest, len, options) 139 | defp do_unpack(<< 0xDD, len :: 32-big-unsigned-integer-unit(1), rest :: binary >>, options), do: unpack_array(rest, len, options) 140 | 141 | # map 142 | defp do_unpack(<< 0b1000 :: 4, len :: 4, rest :: binary >>, options), do: unpack_map(rest, len, options) 143 | defp do_unpack(<< 0xDE, len :: 16-big-unsigned-integer-unit(1), rest :: binary >>, options), do: unpack_map(rest, len, options) 144 | defp do_unpack(<< 0xDF, len :: 32-big-unsigned-integer-unit(1), rest :: binary >>, options), do: unpack_map(rest, len, options) 145 | 146 | defp do_unpack(<< 0xD4, type :: 8, data :: 1-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 147 | unpack_ext(unpacker, type, data, rest) 148 | end 149 | defp do_unpack(<< 0xD5, type :: 8, data :: 2-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 150 | unpack_ext(unpacker, type, data, rest) 151 | end 152 | defp do_unpack(<< 0xD6, type :: 8, data :: 4-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 153 | unpack_ext(unpacker, type, data, rest) 154 | end 155 | defp do_unpack(<< 0xD7, type :: 8, data :: 8-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 156 | unpack_ext(unpacker, type, data, rest) 157 | end 158 | defp do_unpack(<< 0xD8, type :: 8, data :: 16-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 159 | unpack_ext(unpacker, type, data, rest) 160 | end 161 | defp do_unpack(<< 0xC7, len :: 8-unsigned-integer-unit(1), type :: 8, data :: size(len)-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 162 | unpack_ext(unpacker, type, data, rest) 163 | end 164 | defp do_unpack(<< 0xC8, len :: 16-big-unsigned-integer-unit(1), type :: 8, data :: size(len)-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 165 | unpack_ext(unpacker, type, data, rest) 166 | end 167 | defp do_unpack(<< 0xC9, len :: 32-big-unsigned-integer-unit(1), type :: 8, data :: size(len)-binary, rest :: binary >>, %{ext_unpacker: unpacker}) do 168 | unpack_ext(unpacker, type, data, rest) 169 | end 170 | 171 | # invalid prefix 172 | defp do_unpack(<< 0xC1, _ :: binary >>, _), do: { :error, { :invalid_prefix, 0xC1 } } 173 | 174 | defp do_unpack(_, _), do: { :error, :incomplete } 175 | 176 | defp unpack_array(binary, len, options) do 177 | do_unpack_array(binary, len, [], options) 178 | end 179 | 180 | defp do_unpack_array(rest, 0, acc, _) do 181 | { :lists.reverse(acc), rest } 182 | end 183 | 184 | defp do_unpack_array(binary, len, acc, options) do 185 | case do_unpack(binary, options) do 186 | { :error, _ } = error -> 187 | error 188 | { term, rest } -> 189 | do_unpack_array(rest, len - 1, [term|acc], options) 190 | end 191 | end 192 | 193 | defp unpack_map(binary, len, options) do 194 | do_unpack_map(binary, len, [], options) 195 | end 196 | 197 | defp do_unpack_map(rest, 0, acc, _) do 198 | { Enum.into(acc,%{}), rest } 199 | end 200 | 201 | defp do_unpack_map(binary, len, acc, options) do 202 | case do_unpack(binary, options) do 203 | { :error, _ } = error -> 204 | error 205 | { key, rest } -> 206 | case do_unpack(rest, options) do 207 | { :error, _ } = error -> 208 | error 209 | { value, rest } -> 210 | do_unpack_map(rest, len - 1, [{key, value}|acc], options) 211 | end 212 | end 213 | end 214 | 215 | def unpack_ext(nil, _, _, _), do: { :error, :undefined_ext } 216 | def unpack_ext(unpacker, type, data, rest) when is_function(unpacker) do 217 | case unpacker.(type, data) do 218 | { :ok, term } -> 219 | { term, rest } 220 | { :error, _ } = error -> 221 | error 222 | end 223 | end 224 | end 225 | -------------------------------------------------------------------------------- /mix.exs: -------------------------------------------------------------------------------- 1 | defmodule MessagePack.Mixfile do 2 | use Mix.Project 3 | 4 | def project do 5 | [ app: :message_pack, 6 | version: "0.2.0", 7 | elixir: "~> 1.0.0 or ~> 0.15.1", 8 | deps: deps, 9 | build_per_environment: false, 10 | 11 | name: "MessagePack", 12 | source_url: "https://github.com/mururu/msgpack-elixir", 13 | description: "MessagePack Implementation for Elixir", 14 | package: package ] 15 | end 16 | 17 | def application do 18 | [] 19 | end 20 | 21 | defp deps do 22 | [{ :excheck, "~> 0.2.0", only: :test }, 23 | {:triq, github: "krestenkrab/triq", only: :test}, 24 | { :poison, "~> 1.2.0", only: :test },] 25 | end 26 | 27 | defp package do 28 | [ contributors: ["Yuki Ito"], 29 | licenses: ["MIT"], 30 | links: %{ "GitHub" => "https://github.com/mururu/msgpack-elixir"} ] 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /test/cases.json: -------------------------------------------------------------------------------- 1 | [false,true,null,0,0,0,0,0,0,0,0,0,-1,-1,-1,-1,-1,127,127,255,65535,4294967295,-32,-32,-128,-32768,-2147483648,0.0,-0.0,1.0,-1.0,"a","a","a","","","",[0],[0],[0],[],[],[],{},{},{},{"a":97},{"a":97},{"a":97},[[]],[["a"]]] 2 | -------------------------------------------------------------------------------- /test/cases.msg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mururu/msgpack-elixir/4f0063f5e7b010dcd3e8ca8f1b8650f67ea08dea/test/cases.msg -------------------------------------------------------------------------------- /test/message_pack_cases_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MessagePackCasesTest do 2 | use ExUnit.Case 3 | 4 | defp unpack_all(binary) do 5 | do_unpack_all(binary, []) |> Enum.reverse 6 | end 7 | 8 | defp do_unpack_all("", acc), do: acc 9 | defp do_unpack_all(binary, acc) do 10 | { term, rest } = MessagePack.unpack_once!(binary) 11 | do_unpack_all(rest, [term|acc]) 12 | end 13 | 14 | test "compare with json" do 15 | from_msg = Path.expand("cases.msg", __DIR__) 16 | |> File.read! 17 | |> unpack_all 18 | 19 | from_json = Path.expand("cases.json", __DIR__) 20 | |> File.read! 21 | |> Poison.decode! 22 | 23 | Enum.zip(from_msg, from_json) |> Enum.map fn({term1, term2})-> 24 | assert term1 == term2 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/message_pack_ext_test.exs: -------------------------------------------------------------------------------- 1 | defmodule TestExt do 2 | use MessagePack.Ext.Behaviour 3 | 4 | def pack({ :ref, term }), do: { :ok, { 1, :erlang.term_to_binary(term) } } 5 | def pack({ :pid, term }), do: { :ok, { 2, :erlang.term_to_binary(term) } } 6 | 7 | def unpack(1, bin), do: { :ok, { :ref, :erlang.binary_to_term(bin) } } 8 | def unpack(2, bin), do: { :ok, { :pid, :erlang.binary_to_term(bin) } } 9 | end 10 | 11 | defmodule MessagePackExtTest do 12 | use ExUnit.Case 13 | 14 | test "ext mod" do 15 | options = [ext: TestExt] 16 | 17 | oref = make_ref 18 | assert { :ok, { :ref, ref } } = MessagePack.pack!({ :ref, oref }, options) |> MessagePack.unpack(options) 19 | assert is_reference(ref) 20 | assert ref == oref 21 | 22 | opid = self 23 | assert { :ok, { :pid, pid } } = MessagePack.pack!({ :pid, opid }, options) |> MessagePack.unpack(options) 24 | assert is_pid(pid) 25 | assert pid == opid 26 | end 27 | 28 | test "ext fun" do 29 | packer = fn 30 | ({ :ref, ref }) -> { :ok, { 1, :erlang.term_to_binary(ref) } } 31 | ({ :pid, pid }) -> { :ok, { 2, :erlang.term_to_binary(pid) } } 32 | end 33 | 34 | unpacker = fn 35 | (1, bin) -> { :ok, { :ref, :erlang.binary_to_term(bin) } } 36 | (2, bin) -> { :ok, { :pid, :erlang.binary_to_term(bin) } } 37 | end 38 | 39 | options = [ext: [packer: packer, unpacker: unpacker]] 40 | 41 | oref = make_ref 42 | assert { :ok, { :ref, ref } } = MessagePack.pack!({ :ref, oref }, options) |> MessagePack.unpack(options) 43 | assert is_reference(ref) 44 | assert ref == oref 45 | 46 | opid = self 47 | assert { :ok, { :pid, pid } } = MessagePack.pack!({ :pid, opid }, options) |> MessagePack.unpack(options) 48 | assert is_pid(pid) 49 | assert pid == opid 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/message_pack_proper_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MessagePackProperTest do 2 | use ExUnit.Case, async: false 3 | use ExCheck 4 | 5 | property :bijective_packing do 6 | for_all term in msgpack do 7 | { :ok, bin } = MessagePack.pack(term) 8 | { :ok, term2 } = MessagePack.unpack(bin) 9 | term == term2 10 | end 11 | end 12 | 13 | property :bijective_str_packing do 14 | for_all term in msgpack do 15 | { :ok, bin } = MessagePack.pack(term, enable_string: true) 16 | { :ok, term2 } = MessagePack.unpack(bin, enable_string: true) 17 | term == term2 18 | end 19 | end 20 | 21 | defp msgpack do 22 | oneof([ 23 | nil, 24 | bool, 25 | real, 26 | positive_fixint, 27 | uint8, 28 | uint16, 29 | uint32, 30 | uint64, 31 | negative_fixint, 32 | int8, 33 | int16, 34 | int32, 35 | int64, 36 | fixraw, 37 | raw16, 38 | # raw32, # do not work, too big binary and to long test 39 | fixarray, 40 | fixmap 41 | ]) 42 | end 43 | defp msgpack_atomic do 44 | oneof([nil,bool,real,positive_fixint,uint8,uint16,uint32,uint64, 45 | negative_fixint,int8,int16,int32,int64,fixraw,raw16]) 46 | end 47 | 48 | defp positive_fixint, do: choose(0, 127) 49 | defp uint8, do: choose(128, 0xFF) 50 | defp uint16, do: choose(0x100, 0xFFFF) 51 | defp uint32, do: choose(0x10000, 0xFFFFFFFF) 52 | defp uint64, do: choose(0x100000000, 0xFFFFFFFFFFFFFFFF) 53 | 54 | defp negative_fixint, do: choose(-32, -1) 55 | defp int8, do: choose(-0x80, -33) 56 | defp int16, do: choose(-0x8000, -0x81) 57 | defp int32, do: choose(-0x80000000, -0x8001) 58 | defp int64, do: choose(-0x8000000000000000, -0x80000001) 59 | 60 | defp fixraw do 61 | bind choose(0,31),&binary(&1) 62 | end 63 | defp raw16 do 64 | bind choose(32, 0xFFFF),&binary(&1) 65 | end 66 | #defp raw32 do 67 | # bind uint32,&binary(&1) 68 | #end 69 | 70 | defp fixarray do 71 | bind choose(0, 15), &vector(&1,msgpack_atomic) 72 | end 73 | #defp array16 74 | #defp array32 75 | 76 | defp fixmap do 77 | bind choose(0, 15), fn size-> 78 | bind vector(size,{msgpack_atomic,msgpack_atomic}), fn kv_vector-> 79 | Enum.into(kv_vector,%{}) 80 | end 81 | end 82 | end 83 | #defp map16 84 | #defp map32 85 | end 86 | -------------------------------------------------------------------------------- /test/message_pack_test.exs: -------------------------------------------------------------------------------- 1 | defmodule MessagePackTest do 2 | use ExUnit.Case 3 | 4 | defmacrop check(term, len, options \\ []) do 5 | quote location: :keep, bind_quoted: [term: term, len: len, options: options] do 6 | assert { :ok, bin } = MessagePack.pack(term, options) 7 | assert byte_size(bin) == len 8 | assert { :ok, term2 } = MessagePack.unpack(bin, options) 9 | assert term == term2 10 | end 11 | end 12 | 13 | defmacrop check_raw(num, overhead) do 14 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 15 | check(:binary.copy(<<255>>, num), num + overhead) 16 | end 17 | end 18 | 19 | defmacrop check_str(num, overhead) do 20 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 21 | check(String.duplicate(" ", num), num + overhead, enable_string: true) 22 | end 23 | end 24 | 25 | defmacrop check_bin(num, overhead) do 26 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 27 | check(:binary.copy(<<255>>, num), num + overhead, enable_string: true) 28 | end 29 | end 30 | 31 | defmacrop check_array(num, overhead) do 32 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 33 | check(:lists.duplicate(num, nil), num + overhead) 34 | end 35 | end 36 | 37 | defmacrop check_map(0, overhead), do: 38 | quote(do: check(%{},unquote(overhead))) 39 | defmacrop check_map(num, overhead) do 40 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 41 | check(1..num |> Enum.map(&{&1+0x10000,nil}) |> Enum.into(%{}), num + num*5 + overhead) 42 | end 43 | end 44 | 45 | defmacro check_ext(num, overhead) do 46 | quote location: :keep, bind_quoted: [num: num, overhead: overhead] do 47 | packer = fn({ :ext, term }) -> { :ok, { 1, term } } end 48 | unpacker = fn(1, bin) -> { :ok, { :ext, bin } } end 49 | check({ :ext, :binary.copy(<<255>>, num) }, num + overhead, ext: [packer: packer, unpacker: unpacker]) 50 | end 51 | end 52 | 53 | test "nil" do 54 | check nil, 1 55 | end 56 | 57 | test "true" do 58 | check true, 1 59 | end 60 | 61 | test "false" do 62 | check false, 1 63 | end 64 | 65 | test "zero" do 66 | check 0, 1 67 | end 68 | 69 | test "positive fixnum" do 70 | check 1, 1 71 | check 64, 1 72 | check 127, 1 73 | end 74 | 75 | test "negative fixnum" do 76 | check -1, 1 77 | check -32, 1 78 | end 79 | 80 | test "uint 8" do 81 | check 128, 2 82 | check 0xFF, 2 83 | end 84 | 85 | test "uint 16" do 86 | check 0x100, 3 87 | check 0xFFFF, 3 88 | end 89 | 90 | test "uint 32" do 91 | check 0x10000, 5 92 | check 0xFFFFFFFF, 5 93 | end 94 | 95 | test "unit 64" do 96 | check 0x100000000, 9 97 | check 0xFFFFFFFFFFFFFFFF, 9 98 | end 99 | 100 | test "int 8" do 101 | check -33, 2 102 | check -128, 2 103 | end 104 | 105 | test "int 16" do 106 | check -129, 3 107 | check 0x8000, 3 108 | end 109 | 110 | test "int 32" do 111 | check -0x8001, 5 112 | check -0x80000000, 5 113 | end 114 | 115 | test "int 64" do 116 | check -0x80000001, 9 117 | check -0x8000000000000000, 9 118 | end 119 | 120 | test "float" do 121 | check 1.0, 9 122 | check 0.1, 9 123 | check -0.1, 9 124 | check -1.0, 9 125 | end 126 | 127 | test "fixraw" do 128 | check_raw 0, 1 129 | check_raw 31, 1 130 | end 131 | 132 | test "raw 16" do 133 | check_raw 32, 3 134 | check_raw 0xFFFF, 3 135 | end 136 | 137 | test "raw 32" do 138 | check_raw 0x10000, 5 139 | #check_raw 0xFFFFFFFF, 5 140 | end 141 | 142 | test "fixstr" do 143 | check_str 0, 1 144 | check_str 31, 1 145 | end 146 | 147 | test "str 8" do 148 | check_str 32, 2 149 | check_str 0xFF, 2 150 | end 151 | 152 | test "str 16" do 153 | check_str 0x100, 3 154 | check_str 0xFFFF, 3 155 | end 156 | 157 | test "str 32" do 158 | check_str 0x10000, 5 159 | #check_str 0xFFFFFFFF, 5 160 | end 161 | 162 | test "bin 8" do 163 | check_bin 1, 2 164 | check_bin 0xFF, 2 165 | end 166 | 167 | test "bin 16" do 168 | check_bin 0x100, 3 169 | check_bin 0xFFFF, 3 170 | end 171 | 172 | test "bin 32" do 173 | check_bin 0x10000, 5 174 | #check_bin 0xFFFFFFFF, 5 175 | end 176 | 177 | test "fixarray" do 178 | check_array 0, 1 179 | check_array 15, 1 180 | end 181 | 182 | test "array 16" do 183 | check_array 16, 3 184 | check_array 0xFFFF, 3 185 | end 186 | 187 | test "array 32" do 188 | check_array 0x10000, 5 189 | #check_array 0xFFFFFFFF, 5 190 | end 191 | 192 | test "fixmap" do 193 | check_map 0, 1 194 | check_map 15, 1 195 | end 196 | 197 | test "map 16" do 198 | check_map 16, 3 199 | #check_map 0xFFFF, 3 200 | end 201 | 202 | test "map 32" do 203 | #check_map 0x10000, 5 204 | #check_map 0xFFFFFFFF, 5 205 | end 206 | 207 | test "fixext" do 208 | check_ext 1, 2 209 | check_ext 2, 2 210 | check_ext 4, 2 211 | check_ext 8, 2 212 | check_ext 16, 2 213 | end 214 | 215 | test "ext 8" do 216 | check_ext 17, 3 217 | check_ext 0xFF, 3 218 | end 219 | 220 | test "ext 16" do 221 | check_ext 0x100, 4 222 | check_ext 0xFFFF, 4 223 | end 224 | 225 | test "ext 32" do 226 | check_ext 0x10000, 6 227 | #check_ext 0xFFFFFFFF, 6 228 | end 229 | 230 | 231 | ## error tests 232 | 233 | test "pack too big error" do 234 | assert MessagePack.pack(-0x8000000000000001) == { :error, { :too_big, -0x8000000000000001 } } 235 | assert_raise ArgumentError, fn -> MessagePack.pack!(-0x8000000000000001) end 236 | 237 | assert MessagePack.pack(0x10000000000000000) == { :error, { :too_big, 0x10000000000000000 } } 238 | assert_raise ArgumentError, fn -> MessagePack.pack!(0x10000000000000000) end 239 | end 240 | 241 | test "pack badarg error" do 242 | assert { :error, { :badarg, ref } } = MessagePack.pack(self) 243 | assert is_pid(ref) 244 | 245 | assert_raise ArgumentError, fn -> MessagePack.pack!(self) end 246 | end 247 | 248 | test "pack error nested" do 249 | assert MessagePack.pack([0x10000000000000000]) == { :error, { :too_big, 0x10000000000000000 } } 250 | assert_raise ArgumentError, fn -> MessagePack.pack!([0x10000000000000000]) end 251 | 252 | assert MessagePack.pack(%{0x10000000000000000 => 1}) == { :error, { :too_big, 0x10000000000000000 } } 253 | assert_raise ArgumentError, fn -> MessagePack.pack!(%{0x10000000000000000 => 1}) end 254 | 255 | assert MessagePack.pack(%{1 => 0x10000000000000000}) == { :error, { :too_big, 0x10000000000000000 } } 256 | assert_raise ArgumentError, fn -> MessagePack.pack!(%{1 => 0x10000000000000000}) end 257 | end 258 | 259 | test "upack invalid string error" do 260 | bin = MessagePack.pack!(<<255>>) 261 | assert MessagePack.unpack(bin, enable_string: true) == { :error, { :invalid_string, << 255 >> } } 262 | assert_raise ArgumentError, fn -> MessagePack.unpack!(bin, enable_string: true) end 263 | end 264 | 265 | test "unpack invalid prefix error" do 266 | bin = << 0xC1, 1 >> 267 | assert MessagePack.unpack(bin) == { :error, { :invalid_prefix, 0xC1 } } 268 | assert_raise ArgumentError, fn -> MessagePack.unpack!(bin) end 269 | end 270 | 271 | test "unpack incomlete error" do 272 | bin = << 147, 1, 2 >> 273 | assert MessagePack.unpack(bin) == { :error, :incomplete } 274 | assert_raise ArgumentError, fn -> MessagePack.unpack!(bin) end 275 | end 276 | end 277 | -------------------------------------------------------------------------------- /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start 2 | --------------------------------------------------------------------------------