Skip to content

Commit

Permalink
Add support for IEEE 754 NaN and ±infinity unpacking (#53)
Browse files Browse the repository at this point in the history
  • Loading branch information
CandyGumdrop committed Dec 28, 2020
1 parent 7728859 commit 3b93d43
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 3 deletions.
13 changes: 11 additions & 2 deletions lib/msgpax.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@ defmodule Msgpax do
`false` | boolean | `false`
`-1` | integer | `-1`
`1.25` | float | `1.25`
*N/A* | NaN | `Msgpax.NaN`<sup>1</sup>
*N/A* | +infinity | `Msgpax.Infinity`<sup>1</sup>
*N/A* | -infinity | `Msgpax.NegInfinity`<sup>1</sup>
`:ok` | string | `"ok"`
`Atom` | string | `"Elixir.Atom"`
`"str"` | string | `"str"`
`"\xFF\xFF"` | string | `"\xFF\xFF"`
`#Msgpax.Bin<"\xFF">` | binary | `"\xFF"`<sup>1</sup>
`#Msgpax.Bin<"\xFF">` | binary | `"\xFF"`<sup>2</sup>
`%{foo: "bar"}` | map | `%{"foo" => "bar"}`
`[foo: "bar"]` | map | `%{"foo" => "bar"}`
`[1, true]` | array | `[1, true]`
`#Msgpax.Ext<4, "02:12">` | extension | `#Msgpax.Ext<4, "02:12">`
`#DateTime<2017-12-06 00:00:00Z>` | extension | `#DateTime<2017-12-06 00:00:00Z>`
<sup>1</sup>To deserialize back to `Msgpax.Bin` structs see the `unpack/2` options.
<sup>1</sup>NaN and ±infinity are not enabled by default. See `unpack/2` for for more information.
<sup>2</sup>To deserialize back to `Msgpax.Bin` structs see the `unpack/2` options.
"""

alias __MODULE__.Packer
Expand Down Expand Up @@ -199,6 +204,10 @@ defmodule Msgpax do
* `:ext` - (module) a module that implements the `Msgpax.Ext.Unpacker`
behaviour. For more information, see the docs for `Msgpax.Ext.Unpacker`.
* `:nonfinite_floats` - (boolean) if `true`, deserializes NaN and ±infinity to
"signalling" atoms (see the "Data conversion" section), otherwise errors.
Defaults to `false`.
## Examples
iex> Msgpax.unpack(<<163, "foo">>)
Expand Down
30 changes: 30 additions & 0 deletions lib/msgpax/unpacker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Msgpax.UnpackError do
| {:invalid_format, integer}
| :incomplete
| {:ext_unpack_failure, module, Msgpax.Ext.t()}
| {:nonfinite_float, atom}
}

defexception [:reason]
Expand All @@ -26,13 +27,22 @@ defmodule Msgpax.UnpackError do

{:ext_unpack_failure, module, struct} ->
"module #{inspect(module)} could not unpack extension: #{inspect(struct)}"

{:nonfinite_float, value} ->
"encountered non-finite float value: #{inspect(value)}"
end
end
end

defmodule Msgpax.Unpacker do
@moduledoc false

alias Msgpax.{
NaN,
Infinity,
NegInfinity
}

def unpack(<<buffer::bits>>, options) do
unpack(buffer, [], Map.new(options), [], 0, 1)
end
Expand Down Expand Up @@ -63,6 +73,18 @@ defmodule Msgpax.Unpacker do
quote(do: <<0xD2, value::32-signed>>),
quote(do: <<0xD3, value::64-signed>>)
] => quote(do: value),
[
quote(do: <<0xCA, 0::1, 0xFF, 0::23>>),
quote(do: <<0xCB, 0::1, 0xFF, 0b111::3, 0::52>>)
] => quote(do: unpack_float(Infinity, var!(options))),
[
quote(do: <<0xCA, 1::1, 0xFF, 0::23>>),
quote(do: <<0xCB, 1::1, 0xFF, 0b111::3, 0::52>>)
] => quote(do: unpack_float(NegInfinity, var!(options))),
[
quote(do: <<0xCA, _sign::1, 0xFF, _fraction::23>>),
quote(do: <<0xCB, _sign::1, 0xFF, 0b111::3, _fraction::52>>)
] => quote(do: unpack_float(NaN, var!(options))),
# Negative fixint
[quote(do: <<0b111::3, value::5>>)] => quote(do: value - 0b100000)
}
Expand Down Expand Up @@ -186,6 +208,14 @@ defmodule Msgpax.Unpacker do
content
end

defp unpack_float(value, %{nonfinite_floats: true}) do
value
end

defp unpack_float(value, _options) do
throw({:nonfinite_float, value})
end

defp unpack_ext(type, content, options) do
if type < 128 do
type
Expand Down
18 changes: 17 additions & 1 deletion test/msgpax_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,24 @@ defmodule MsgpaxTest do
assert_format Atom, <<171>>, "Elixir.Atom"
end

test "float" do
test "floats" do
assert_format 42.1, <<203>>

for {packed, value} <- %{
# 32-bit.
<<202, 0x7FC00000::32>> => Msgpax.NaN,
<<202, 0x7F800000::32>> => Msgpax.Infinity,
<<202, 0xFF800000::32>> => Msgpax.NegInfinity,
# 64-bit.
<<203, 0x7FF8::16, 0::48>> => Msgpax.NaN,
<<203, 0x7FF0::16, 0::48>> => Msgpax.Infinity,
<<203, 0xFFF0::16, 0::48>> => Msgpax.NegInfinity
} do
assert Msgpax.unpack(packed) ==
{:error, %Msgpax.UnpackError{reason: {:nonfinite_float, value}}}

assert Msgpax.unpack(packed, nonfinite_floats: true) == {:ok, value}
end
end

test "positive fixint" do
Expand Down

0 comments on commit 3b93d43

Please sign in to comment.