defmodule Openflow.Match do @match_size 8 @header_size 4 defstruct( type: :oxm, fields: [] ) alias __MODULE__ def new(fields \\ []) do oxm_fields = fields |> keyword_to_oxm_fields([]) %Match{type: :oxm, fields: oxm_fields} end def read(binary) do <<1::16, no_pad_len::16, binary1::binary>> = binary padding_length = Openflow.Utils.pad_length(no_pad_len, 8) match_field_len = no_pad_len - @header_size <> = binary1 {decode_fields(match_fields, []), rest} end def to_binary(%Match{fields: fields}) do fields_bin = encode_fields(fields, <<>>) length = byte_size(fields_bin) + @match_size - @header_size type_int = Openflow.Enums.to_int(:oxm, :match_type) padding = Openflow.Utils.padding(length, 8) <> end def codec_header(oxm_field0) when is_atom(oxm_field0) do oxm_field = case has_mask(oxm_field0) do 1 -> string = to_string(oxm_field0) "masked_" <> field = string String.to_atom(field) 0 -> oxm_field0 end case Openflow.Match.Field.vendor_of(oxm_field) do oxm_class when oxm_class in [:nxm_0, :nxm_1, :openflow_basic, :packet_register] -> oxm_class_int = Openflow.Enums.to_int(oxm_class, :oxm_class) oxm_field_int = Openflow.Enums.to_int(oxm_field, oxm_class) oxm_length = div(Openflow.Match.Field.n_bits_of(oxm_field), 8) has_mask = has_mask(oxm_field0) <> experimenter when experimenter in [:nicira_ext_match, :onf_ext_match, :hp_ext_match] -> oxm_class_int = 0xFFFF experimenter_int = Openflow.Enums.to_int(experimenter, :experimenter_oxm_vendors) oxm_field_int = Openflow.Enums.to_int(oxm_field, experimenter) oxm_length = div(Openflow.Match.Field.n_bits_of(oxm_field) + 4, 8) has_mask = has_mask(oxm_field0) <> end end def codec_header(<>) do oxm_class = Openflow.Enums.to_atom(oxm_class_int, :oxm_class) case oxm_has_mask do 0 -> Openflow.Enums.to_atom(oxm_field_int, oxm_class) 1 -> field_str = oxm_field_int |> Openflow.Enums.to_atom(oxm_class) |> to_string String.to_atom("masked_" <> field_str) end end def codec_header( <<0xFFFF::16, oxm_field_int::7, oxm_has_mask::1, _oxm_length::8, experimenter_int::32>> ) do experimenter = Openflow.Enums.to_atom(experimenter_int, :experimenter_oxm_vendors) Openflow.Enums.to_atom(oxm_field_int, experimenter) case oxm_has_mask do 0 -> Openflow.Enums.to_atom(oxm_field_int, experimenter) 1 -> field_str = oxm_field_int |> Openflow.Enums.to_atom(experimenter) |> to_string String.to_atom("masked_" <> field_str) end end def header_size( <<_oxm_class_int::16, _oxm_field_int::7, _oxm_has_mask::1, _oxm_length::8, _::bytes>> ), do: 4 def header_size( <<0xFFFF::16, _oxm_field_int::7, _oxm_has_mask::1, _oxm_length::8, _exp_int::32, _::bytes>> ), do: 8 # private functions defp decode_fields(<<>>, acc), do: Enum.reverse(acc) defp decode_fields( <<0xFFFF::16, _::7, 1::1, length::8, vendor_int::32, field_int::16, binary::bytes>>, acc ) do length = length - 6 field_len = div(length, 2) <> = binary experimenter = Openflow.Enums.to_atom(vendor_int, :experimenter_oxm_vendors) field_name = Openflow.Enums.to_atom(field_int, experimenter) value = Openflow.Match.Field.codec(value_bin, field_name) mask = Openflow.Match.Field.codec(mask_bin, field_name) decode_fields(rest, [{field_name, {value, mask}} | acc]) end defp decode_fields( <<0xFFFF::16, _::7, 0::1, length::8, vendor_int::32, field_int::16, binary::bytes>>, acc ) do length = length - 6 <> = binary experimenter = Openflow.Enums.to_atom(vendor_int, :experimenter_oxm_vendors) field_name = Openflow.Enums.to_atom(field_int, experimenter) value = Openflow.Match.Field.codec(value_bin, field_name) decode_fields(rest, [{field_name, value} | acc]) end defp decode_fields(<>, acc) do field_len = div(length, 2) <> = binary class = Openflow.Enums.to_atom(class_int, :oxm_class) field_name = Openflow.Enums.to_atom(field_int, class) value = Openflow.Match.Field.codec(value_bin, field_name) mask = Openflow.Match.Field.codec(mask_bin, field_name) decode_fields(rest, [{field_name, {value, mask}} | acc]) end defp decode_fields(<>, acc) do <> = binary class = Openflow.Enums.to_atom(class_int, :oxm_class) field_name = Openflow.Enums.to_atom(field_int, class) value = Openflow.Match.Field.codec(value_bin, field_name) decode_fields(rest, [{field_name, value} | acc]) end defp encode_fields([], acc), do: acc defp encode_fields([field | fields], acc) do encode_fields(fields, <>) end defp encode_field(%{class: class, field: field, has_mask: true, value: value, mask: mask}) when class == :nicira_ext_match or class == :onf_ext_match do vendor_int = Openflow.Enums.to_int(class, :experimenter_oxm_vendors) field_int = Openflow.Enums.to_int(field, class) has_mask_int = 1 length = byte_size(value) * 2 + 6 <<0xFFFF::16, 0::7, has_mask_int::1, length::8, vendor_int::32, field_int::16, value::bytes, mask::bytes>> end defp encode_field(%{class: class, field: field, has_mask: false, value: value}) when class == :nicira_ext_match or class == :onf_ext_match do vendor_int = Openflow.Enums.to_int(class, :experimenter_oxm_vendors) field_int = Openflow.Enums.to_int(field, class) has_mask_int = 0 length = byte_size(value) + 6 <<0xFFFF::16, 0::7, has_mask_int::1, length::8, vendor_int::32, field_int::16, value::bytes>> end defp encode_field(%{class: class, field: field, has_mask: true, value: value, mask: mask}) do class_int = Openflow.Enums.to_int(class, :oxm_class) field_int = Openflow.Enums.to_int(field, class) has_mask_int = 1 length = byte_size(value) * 2 <> end defp encode_field(%{class: class, field: field, has_mask: false, value: value}) do class_int = Openflow.Enums.to_int(class, :oxm_class) field_int = Openflow.Enums.to_int(field, class) has_mask_int = 0 length = byte_size(value) <> end defp keyword_to_oxm_fields([], acc), do: Enum.reverse(acc) defp keyword_to_oxm_fields([{field_name, field_value} | fields], acc) do keyword_to_oxm_fields(fields, [oxm_field(field_name, field_value) | acc]) end defp oxm_field(field_name, {value, mask}) do value_bin = Openflow.Match.Field.codec(value, field_name) mask_bin = Openflow.Match.Field.codec(mask, field_name) match_class = Openflow.Match.Field.vendor_of(field_name) %{class: match_class, field: field_name, has_mask: true, value: value_bin, mask: mask_bin} end defp oxm_field(field_name, value) do value_bin = Openflow.Match.Field.codec(value, field_name) match_class = Openflow.Match.Field.vendor_of(field_name) %{class: match_class, field: field_name, has_mask: false, value: value_bin} end def has_mask(oxm_field) when is_atom(oxm_field) do has_mask? = oxm_field |> to_string |> String.match?(~r/^masked_/) if has_mask? do 1 else 0 end end end