Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ahamez committed Dec 14, 2016
0 parents commit d76eed4
Show file tree
Hide file tree
Showing 17 changed files with 1,288 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/_build
/cover
/deps
/doc
erl_crash.dump
*.ez
*.beam
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2016 Alexandre Hamez

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Protox

**TODO: Add description**

## Installation

If [available in Hex](https://hex.pm/docs/publish), the package can be installed as:

1. Add `protox` to your list of dependencies in `mix.exs`:

```elixir
def deps do
[{:protox, "~> 0.1.0"}]
end
```

2. Ensure `protox` is started before your application:

```elixir
def application do
[applications: [:protox]]
end
```

30 changes: 30 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# This file is responsible for configuring your application
# and its dependencies with the aid of the Mix.Config module.
use Mix.Config

# This configuration is loaded before any dependency and is restricted
# to this project. If another project depends on this project, this
# file won't be loaded nor affect the parent project. For this reason,
# if you want to provide default values for your application for
# 3rd-party users, it should be done in your "mix.exs" file.

# You can configure for your application as:
#
# config :protox, key: :value
#
# And access this configuration in your application as:
#
# Application.get_env(:protox, :key)
#
# Or configure a 3rd-party app:
#
# config :logger, level: :info
#

# It is also possible to import configuration files, relative to this
# directory. For example, you can emulate configuration per environment
# by uncommenting the line below and defining dev.exs, test.exs and such.
# Configuration from the imported file will override the ones defined
# here (which is why it is important to import them last).
#
# import_config "#{Mix.env}.exs"
2 changes: 2 additions & 0 deletions lib/protox.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
defmodule Protox do
end
175 changes: 175 additions & 0 deletions lib/protox/decode.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
defmodule Protox.Decode do

require Protox.Util


alias Protox.{
Enumeration,
Message,
Util,
}
import Util


def decode(bytes, defs) do
parse_key_value(bytes, defs, struct(defs.name))
end


# -- Private


defp parse_key_value(<<>>, _, msg) do
msg
end
defp parse_key_value(bytes, defs, msg) do
{tag, wire_type, rest} = parse_key(bytes)

field = defs.fields[tag]
{new_msg, new_rest} = if field do
{value, new_rest} = parse_value(rest, wire_type, field.type)
{set_field(msg, field, value), new_rest}
else
# TODO. Keep unknown bytes?
{msg, parse_unknown(wire_type, rest)}
end

parse_key_value(new_rest, defs, new_msg)
end


# Get the key's tag and wire type.
defp parse_key(bytes) do
use Bitwise
{key, rest} = Varint.LEB128.decode(bytes)
{key >>> 3, key &&& 0b111, rest}
end


# Wire type 0: varint.
defp parse_value(bytes, 0, type) do
{value, rest} = Varint.LEB128.decode(bytes)
{varint_value(value, type), rest}
end


# Wire type 1: fixed 64-bit.
defp parse_value(<<value::float-little-64, rest::binary>>, 1, :double) do
{value, rest}
end
defp parse_value(<<value::little-64, rest::binary>>, 1, _) do
{value, rest}
end


# Wire type 2: length-delimited.
defp parse_value(bytes, 2, type) do
{len, new_bytes} = Varint.LEB128.decode(bytes)
<<delimited::binary-size(len), rest::binary>> = new_bytes
{parse_delimited(delimited, type), rest}
end


# Wire type 5: fixed 32-bit.
defp parse_value(<<value::float-little-32, rest::binary>>, 5, :float) do
{value, rest}
end
defp parse_value(<<value::little-32, rest::binary>>, 5, _) do
{value, rest}
end


defp parse_delimited(bytes, type) when is_primitive_varint(type) do
parse_repeated_varint([], bytes, type)
end
defp parse_delimited(bytes, type) when is_primitive_fixed(type) do
parse_repeated_fixed([], bytes, type)
end
defp parse_delimited(bytes, type) when type == :string or type == :bytes do
bytes
end
defp parse_delimited(bytes, type = %Message{}) do
decode(bytes, type)
end


defp parse_repeated_varint(acc, <<>>, _) do
Enum.reverse(acc)
end
defp parse_repeated_varint(acc, bytes, type) do
{value, rest} = Varint.LEB128.decode(bytes)
parse_repeated_varint([varint_value(value, type)|acc], rest, type)
end


defp parse_repeated_fixed(acc, <<>>, _) do
Enum.reverse(acc)
end
defp parse_repeated_fixed(acc, <<value::signed-little-64, rest::binary>>, ty)
when ty == :fixed64 or ty == :sfixed64 do
parse_repeated_fixed([value|acc], rest, ty)
end
defp parse_repeated_fixed(acc, <<value::signed-little-32, rest::binary>>, ty)
when ty == :fixed32 or ty == :sfixed32 do
parse_repeated_fixed([value|acc], rest, ty)
end
defp parse_repeated_fixed(acc, <<value::float-little-64, rest::binary>>, :double) do
parse_repeated_fixed([value|acc], rest, :double)
end
defp parse_repeated_fixed(acc, <<value::float-little-32, rest::binary>>, :float) do
parse_repeated_fixed([value|acc], rest, :float)
end


defp varint_value(value, :bool) , do: value == 1
defp varint_value(value, :sint32) , do: Varint.Zigzag.decode(value)
defp varint_value(value, :sint64) , do: Varint.Zigzag.decode(value)
defp varint_value(value, :uint32) , do: value
defp varint_value(value, :uint64) , do: value
defp varint_value(value, e = %Enumeration{}), do: Map.get(e.members, value, value)
defp varint_value(value, :int32) do
<<res::signed-32>> = <<value::32>>
res
end
defp varint_value(value, :int64) do
<<res::signed-64>> = <<value::64>>
res
end


defp parse_unknown(0, bytes) , do: get_varint_bytes(bytes)
defp parse_unknown(1, <<_::64, rest::binary>>), do: rest
defp parse_unknown(5, <<_::32, rest::binary>>), do: rest
defp parse_unknown(2, bytes) do
{len, new_bytes} = Varint.LEB128.decode(bytes)
<<_::binary-size(len), rest::binary>> = new_bytes
rest
end


defp get_varint_bytes(<<0::1, _::7, rest::binary>>), do: rest
defp get_varint_bytes(<<1::1, _::7, rest::binary>>), do: get_varint_bytes(rest)


# Set the field correponding to `tag` in `msg` with `value`.
defp set_field(msg, field, value) do
{f, v} = case field.kind do
:map ->
previous = Map.fetch!(msg, field.name)
{field.name, Map.put(previous, value.key, value.value)}

{:oneof, parent_field} ->
{parent_field, {field.name, value}}

:repeated ->
previous = Map.fetch!(msg, field.name)
{field.name, previous ++ List.wrap(value)}

:normal ->
{field.name, value}
end

struct!(msg, [{f, v}])
end

end
26 changes: 26 additions & 0 deletions lib/protox/default.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
defmodule Protox.Default do

alias Protox.{
Enumeration,
Message,
}

def default(:bool) , do: false
def default(:int32) , do: 0
def default(:uint32) , do: 0
def default(:int64) , do: 0
def default(:uint64) , do: 0
def default(:sint32) , do: 0
def default(:sint64) , do: 0
def default(:fixed64) , do: 0
def default(:sfixed64) , do: 0
def default(:fixed32) , do: 0
def default(:sfixed32) , do: 0
def default(:double) , do: 0
def default(:float) , do: 0
def default(:string) , do: ""
def default(:bytes) , do: <<>>
def default(e = %Enumeration{}), do: Map.fetch!(e.members, 0)
def default(%Message{}) , do: nil

end
22 changes: 22 additions & 0 deletions lib/protox/defs.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule Protox.Field do
@enforce_keys [:name, :kind, :type]
defstruct name: nil,
kind: nil,
type: nil
# repeated: false,
# map: false,
# oneof: nil
end

defmodule Protox.Message do
@enforce_keys [:name, :fields, :tags]
defstruct name: nil,
fields: %{},
tags: %{}
end

defmodule Protox.Enumeration do
@enforce_keys [:members]
defstruct members: %{},
values: %{}
end
Loading

0 comments on commit d76eed4

Please sign in to comment.