diff --git a/lib/openflow/instruction.ex b/lib/openflow/instruction.ex index b64b1c7..063de39 100644 --- a/lib/openflow/instruction.ex +++ b/lib/openflow/instruction.ex @@ -1,18 +1,31 @@ defmodule Openflow.Instruction do - def read(instruction_bin) do - do_read([], instruction_bin) - end + @moduledoc """ + Openflow instruction codec handler + """ - def to_binary(instructions) when is_list(instructions) do - to_binary(<<>>, instructions) - end + @type instruction :: + Openflow.Instruction.ApplyActions.t() + | Openflow.Instruction.WriteActions.t() + | Openflow.Instruction.ClearActions.t() + | Openflow.Instruction.GotoTable.t() + | Openflow.Instruction.WriteMetadata.t() + | Openflow.Instruction.Meter.t() - def to_binary(instruction) do - to_binary([instruction]) - end + @spec read(<<_::32, _::_*8>>) :: [instruction()] + def read(instruction_bin), + do: do_read([], instruction_bin) + + @spec to_binary(instruction()) :: <<_::32, _::_*8>> + def to_binary(instructions) when is_list(instructions), + do: to_binary(<<>>, instructions) + + @spec to_binary([instruction()]) :: <<_::32, _::_*8>> + def to_binary(instruction), + do: to_binary([instruction]) # private functions + @spec do_read([instruction()], <<_::_*8>>) :: [instruction()] defp do_read(acc, <<>>), do: Enum.reverse(acc) defp do_read(acc, <> = binary) do @@ -21,6 +34,7 @@ defmodule Openflow.Instruction do do_read([codec.read(instruction_bin) | acc], rest) end + @spec to_binary(<<_::_*8>>) :: [instruction()] defp to_binary(acc, []), do: acc defp to_binary(acc, [instruction | rest]) do diff --git a/lib/openflow/instructions/apply_actions.ex b/lib/openflow/instructions/apply_actions.ex index b331419..0def15d 100644 --- a/lib/openflow/instructions/apply_actions.ex +++ b/lib/openflow/instructions/apply_actions.ex @@ -3,16 +3,19 @@ defmodule Openflow.Instruction.ApplyActions do alias __MODULE__ - def new(actions) do - %ApplyActions{actions: actions} - end + @type t :: %ApplyActions{actions: [Openflow.Action.type()]} + @spec new(Openflow.Action.type() | [Openflow.Action.type()]) :: t() + def new(actions), do: %ApplyActions{actions: actions} + + @spec to_binary(t()) :: <<_::64, _::_*8>> def to_binary(%ApplyActions{actions: actions}) do actions_bin = Openflow.Action.to_binary(actions) length = 8 + byte_size(actions_bin) <<4::16, length::16, 0::size(4)-unit(8), actions_bin::bytes>> end + @spec read(<<_::64, _::_*8>>) :: t() def read(<<4::16, _length::16, _::size(4)-unit(8), actions_bin::bytes>>) do actions = Openflow.Action.read(actions_bin) %ApplyActions{actions: actions} diff --git a/lib/openflow/instructions/clear_actions.ex b/lib/openflow/instructions/clear_actions.ex index 6d52c08..4ff2cf3 100644 --- a/lib/openflow/instructions/clear_actions.ex +++ b/lib/openflow/instructions/clear_actions.ex @@ -3,17 +3,17 @@ defmodule Openflow.Instruction.ClearActions do alias __MODULE__ - def new do - %ClearActions{} - end + @type t :: %ClearActions{} - def to_binary(%ClearActions{}) do - actions_bin = "" - length = 8 + byte_size(actions_bin) - <<5::16, length::16, 0::size(4)-unit(8), actions_bin::bytes>> - end + @spec new() :: t() + def new, + do: %ClearActions{} - def read(<<5::16, _length::16, _::size(4)-unit(8), _::bytes>>) do - %ClearActions{} - end + @spec to_binary(t()) :: <<_::64>> + def to_binary(%ClearActions{}), + do: <<5::16, 8::16, 0::32>> + + @spec read(<<_::64, _::_*8>>) :: t() + def read(<<5::16, _length::16, _::size(4)-unit(8), _::bytes>>), + do: %ClearActions{} end diff --git a/lib/openflow/instructions/experimenter.ex b/lib/openflow/instructions/experimenter.ex index f4cc3f2..c2c65bf 100644 --- a/lib/openflow/instructions/experimenter.ex +++ b/lib/openflow/instructions/experimenter.ex @@ -3,15 +3,19 @@ defmodule Openflow.Instruction.Experimenter do alias __MODULE__ - def new(exp_id, data \\ "") do - %Experimenter{exp_id: exp_id, data: data} - end + @type t :: %Experimenter{exp_id: 0..0xFFFFFFFF, data: binary()} + @spec new(exp_id :: 0..0xFFFFFFFF, data :: String.t()) :: t() + def new(exp_id, data \\ ""), + do: %Experimenter{exp_id: exp_id, data: data} + + @spec to_binary(t()) :: <<_::64, _::_*8>> def to_binary(%Experimenter{exp_id: exp_id, data: data}) do length = 8 + byte_size(data) <<0xFFFF::16, length::16, exp_id::32, data::bytes>> end + @spec read(<<_::64, _::_*8>>) :: t() def read(<<0xFFFF::16, _::16, exp_id::32, data::bytes>>) do %Experimenter{exp_id: exp_id, data: data} end diff --git a/lib/openflow/instructions/goto_table.ex b/lib/openflow/instructions/goto_table.ex index f78c2c9..aec4051 100644 --- a/lib/openflow/instructions/goto_table.ex +++ b/lib/openflow/instructions/goto_table.ex @@ -3,15 +3,18 @@ defmodule Openflow.Instruction.GotoTable do alias __MODULE__ - def new(table_id) do - %GotoTable{table_id: table_id} - end + @type t :: %GotoTable{table_id: 0..0xFF} + @spec new(table_id :: 0..0xFF) :: t() + def new(table_id), do: %GotoTable{table_id: table_id} + + @spec to_binary(t()) :: <<_::64>> def to_binary(%GotoTable{table_id: table_id}) do table_id_int = Openflow.Utils.get_enum(table_id, :table_id) <<1::16, 8::16, table_id_int::8, 0::size(3)-unit(8)>> end + @spec read(<<_::64>>) :: t() def read(<<1::16, 8::16, table_id_int::8, _::size(3)-unit(8)>>) do table_id = Openflow.Utils.get_enum(table_id_int, :table_id) %GotoTable{table_id: table_id} diff --git a/lib/openflow/instructions/meter.ex b/lib/openflow/instructions/meter.ex index f038fc9..8e6f6f9 100644 --- a/lib/openflow/instructions/meter.ex +++ b/lib/openflow/instructions/meter.ex @@ -3,15 +3,18 @@ defmodule Openflow.Instruction.Meter do alias __MODULE__ - def new(meter_id) do - %Meter{meter_id: meter_id} - end + @type t :: %Meter{meter_id: 0..0xFFFFFFFF} + @spec new(meter_id :: 0..0xFFFFFFFF) :: t() + def new(meter_id), do: %Meter{meter_id: meter_id} + + @spec to_binary(t()) :: <<_::64>> def to_binary(%Meter{meter_id: meter_id}) do meter_id_int = Openflow.Utils.get_enum(meter_id, :meter_id) <<6::16, 8::16, meter_id_int::32>> end + @spec read(<<_::64>>) :: t() def read(<<6::16, _::16, meter_id_int::32>>) do meter_id = Openflow.Utils.get_enum(meter_id_int, :meter_id) %Meter{meter_id: meter_id} diff --git a/lib/openflow/instructions/write_actions.ex b/lib/openflow/instructions/write_actions.ex index fb9eeea..827bfe9 100644 --- a/lib/openflow/instructions/write_actions.ex +++ b/lib/openflow/instructions/write_actions.ex @@ -3,16 +3,19 @@ defmodule Openflow.Instruction.WriteActions do alias __MODULE__ - def new(actions) do - %WriteActions{actions: actions} - end + @type t :: %WriteActions{actions: [Openflow.Action.type()]} + @spec new(Openflow.Action.type() | [Openflow.Action.type()]) :: t() + def new(actions), do: %WriteActions{actions: actions} + + @spec to_binary(t()) :: <<_::32, _::_*8>> def to_binary(%WriteActions{actions: actions}) do actions_bin = Openflow.Action.to_binary(actions) length = 8 + byte_size(actions_bin) <<3::16, length::16, 0::size(4)-unit(8), actions_bin::bytes>> end + @spec read(<<_::32, _::_*8>>) :: t() def read(<<3::16, _length::16, _::size(4)-unit(8), actions_bin::bytes>>) do actions = Openflow.Action.read(actions_bin) %WriteActions{actions: actions} diff --git a/lib/openflow/instructions/write_metadata.ex b/lib/openflow/instructions/write_metadata.ex index 5b0df98..5adf7f0 100644 --- a/lib/openflow/instructions/write_metadata.ex +++ b/lib/openflow/instructions/write_metadata.ex @@ -1,19 +1,31 @@ defmodule Openflow.Instruction.WriteMetadata do - defstruct(metadata: 0, metadata_mask: 0xFFFFFFFFFFFFFFFF) + defstruct(value: 0, mask: 0xFFFFFFFFFFFFFFFF) alias __MODULE__ + @type t :: %WriteMetadata{ + value: 0..0xFFFFFFFFFFFFFFFF, + mask: 0..0xFFFFFFFFFFFFFFFF + } + + @spec new( + value: 0..0xFFFFFFFFFFFFFFFF, + mask: 0..0xFFFFFFFFFFFFFFFF + ) :: t() def new(options) do - metadata = Keyword.get(options, :metadata, 0) - metadata_mask = Keyword.get(options, :metadata_mask, 0xFFFFFFFFFFFFFFFF) - %WriteMetadata{metadata: metadata, metadata_mask: metadata_mask} + %WriteMetadata{ + value: options[:value] || 0, + mask: options[:mask] || 0xFFFFFFFFFFFFFFFF + } end - def to_binary(%WriteMetadata{metadata: metadata, metadata_mask: metadata_mask}) do - <<2::16, 24::16, 0::size(4)-unit(8), metadata::64, metadata_mask::64>> + @spec to_binary(t()) :: <<_::192>> + def to_binary(%WriteMetadata{value: value, mask: mask}) do + <<2::16, 24::16, 0::size(4)-unit(8), value::64, mask::64>> end - def read(<<2::16, 24::16, _::size(4)-unit(8), metadata::64, metadata_mask::64>>) do - %WriteMetadata{metadata: metadata, metadata_mask: metadata_mask} + @spec read(<<_::192>>) :: t() + def read(<<2::16, 24::16, _::size(4)-unit(8), value::64, mask::64>>) do + %WriteMetadata{value: value, mask: mask} end end diff --git a/test/ofp_instruction_test.exs b/test/ofp_instruction_test.exs new file mode 100644 index 0000000..b23bbe8 --- /dev/null +++ b/test/ofp_instruction_test.exs @@ -0,0 +1,35 @@ +defmodule OfpInstructionTest do + use ExUnit.Case + doctest Openflow + + alias Openflow.Instruction.{ + ApplyActions, + WriteActions, + ClearActions, + GotoTable, + WriteMetadata, + Meter, + Experimenter + } + + describe "Openflow.Instruction" do + test "with all instructions parse and generate" do + instructions = [ + ApplyActions.new([Openflow.Action.Output.new(:controller)]), + WriteActions.new([Openflow.Action.Output.new(5)]), + ClearActions.new(), + GotoTable.new(10), + WriteMetadata.new(value: 100, mask: 0xFFFFFFFFFFFFFFFF), + Meter.new(100), + Experimenter.new(0xCAFEBABE, "hogehoge"), + Experimenter.new(0xCAFEBABE) + ] + + instructions + |> Openflow.Instruction.to_binary() + |> Openflow.Instruction.read() + |> Kernel.==(instructions) + |> assert() + end + end +end