defmodule Openflow.Multipart.TableFeatures.Body do defstruct table_id: 0, name: "", metadata_match: 0, metadata_write: 0, config: [], max_entries: 0, instructions: nil, instructions_miss: nil, next_tables: nil, next_tables_miss: nil, write_actions: nil, write_actions_miss: nil, apply_actions: nil, apply_actions_miss: nil, match: nil, wildcards: nil, write_setfield: nil, write_setfield_miss: nil, apply_setfield: nil, apply_setfield_miss: nil, experimenter: nil, experimenter_miss: nil alias __MODULE__ @max_table_name_len 32 @prop_header_length 4 @table_features_length 64 @instructions 0 @instructions_miss 1 @next_tables 2 @next_tables_miss 3 @write_actions 4 @write_actions_miss 5 @apply_actions 6 @apply_actions_miss 7 @match 8 @wildcards 10 @write_setfield 12 @write_setfield_miss 13 @apply_setfield 14 @apply_setfield_miss 15 @experimenter 0xFFFE @experimenter_miss 0xFFFF @prop_keys [ :instructions, :instructions_miss, :next_tables, :next_tables_miss, :write_actions, :write_actions_miss, :apply_actions, :apply_actions_miss, :match, :wildcards, :write_setfield, :write_setfield_miss, :apply_setfield, :apply_setfield_miss, :experimenter, :experimenter_miss ] def new(options) do %Body{ table_id: options[:table_id] || 0, name: options[:name] || "", metadata_match: options[:metadata_match] || 0, metadata_write: options[:metadata_write] || 0, config: options[:config] || [], max_entries: options[:max_entries] || 0, instructions: options[:instructions], instructions_miss: options[:instructions_miss], next_tables: options[:next_tables], next_tables_miss: options[:next_tables_miss], write_actions: options[:write_actions], write_actions_miss: options[:write_actions_miss], apply_actions: options[:apply_actions], apply_actions_miss: options[:apply_actions_miss], match: options[:match], wildcards: options[:wildcards], write_setfield: options[:write_setfield], write_setfield_miss: options[:write_setfield_miss], apply_setfield: options[:apply_setfield], apply_setfield_miss: options[:apply_setfield_miss], experimenter: options[:experimenter], experimenter_miss: options[:experimenter_miss] } end def read(binary) do do_read([], binary) end def to_binary(features) do do_to_binary("", features) end # private functions defp do_to_binary(acc, []), do: acc defp do_to_binary(acc, [table | rest]) do do_to_binary(<>, rest) end defp do_read(acc, ""), do: Enum.reverse(acc) defp do_read(acc, <> = binary) do <> = binary do_read([decode(features_bin) | acc], rest) end defp decode( <<_length::16, table_id::8, _::size(5)-unit(8), name_bin::size(@max_table_name_len)-bytes, metadata_match::64, metadata_write::64, config_int::32, max_entries::32, props_bin::bytes>> ) do name = hd(String.split(name_bin, <<0>>, parts: 2)) config = Openflow.Enums.int_to_flags(config_int, :table_config) body = %Body{ table_id: table_id, name: name, metadata_match: metadata_match, metadata_write: metadata_write, config: config, max_entries: max_entries } decode_props(body, props_bin) end defp encode(table) do filter_fn = fn key -> not is_nil(Map.get(table, key)) end keys = Enum.filter(@prop_keys, filter_fn) props_bin = encode_props("", table, keys) length = @table_features_length + byte_size(props_bin) %Body{ table_id: table_id, name: name, metadata_match: metadata_match, metadata_write: metadata_write, config: config, max_entries: max_entries } = table config_int = Openflow.Enums.flags_to_int(config, :table_config) name_bin = String.pad_trailing(name, @max_table_name_len, <<0>>) <> end defp decode_props(body, ""), do: body defp decode_props(body, <>) when type_int == @instructions or type_int == @instructions_miss do pad_length = Openflow.Utils.pad_length(length, 8) value_length = length - @prop_header_length <> = tail instructions = decode_instructions([], instructions_bin) type = Openflow.Enums.to_atom(type_int, :table_feature_prop_type) body |> struct(%{type => instructions}) |> decode_props(rest) end defp decode_props(body, <>) when type_int == @next_tables or type_int == @next_tables_miss do pad_length = Openflow.Utils.pad_length(length, 8) value_length = length - @prop_header_length <> = tail next_tables = :erlang.binary_to_list(next_tables_bin) type = Openflow.Enums.to_atom(type_int, :table_feature_prop_type) body |> struct(%{type => next_tables}) |> decode_props(rest) end defp decode_props(body, <>) when type_int == @write_actions or type_int == @write_actions_miss or type_int == @apply_actions or type_int == @apply_actions_miss do pad_length = Openflow.Utils.pad_length(length, 8) value_length = length - @prop_header_length <> = tail actions = decode_actions([], actions_bin) type = Openflow.Enums.to_atom(type_int, :table_feature_prop_type) body |> struct(%{type => actions}) |> decode_props(rest) end defp decode_props(body, <>) when type_int == @match or type_int == @wildcards or type_int == @write_setfield or type_int == @write_setfield_miss or type_int == @apply_setfield or type_int == @apply_setfield_miss do pad_length = Openflow.Utils.pad_length(length, 8) value_length = length - @prop_header_length <> = tail matches = decode_matches([], matches_bin) type = Openflow.Enums.to_atom(type_int, :table_feature_prop_type) body |> struct(%{type => matches}) |> decode_props(rest) end defp decode_props( body, <> ) when type_int == @experimenter or type_int == @experimenter_miss do pad_length = Openflow.Utils.pad_length(length, 8) value_length = length - 12 <<_::size(value_length)-bytes, _::size(pad_length)-unit(8), rest::bytes>> = tail decode_props(body, rest) end defp encode_props(acc, _table, []), do: acc defp encode_props(acc, table, [type | rest]) when type == :instructions or type == :instructions_miss do type_int = Openflow.Enums.to_int(type, :table_feature_prop_type) instructions_bin = encode_instructions("", Map.get(table, type)) length = @prop_header_length + byte_size(instructions_bin) pad_length = Openflow.Utils.pad_length(length, 8) body = <> encode_props(<>, table, rest) end defp encode_props(acc, table, [type | rest]) when type == :next_tables or type == :next_tables_miss do type_int = Openflow.Enums.to_int(type, :table_feature_prop_type) next_tables_bin = :erlang.list_to_binary(Map.get(table, type)) length = @prop_header_length + byte_size(next_tables_bin) pad_length = Openflow.Utils.pad_length(length, 8) body = <> encode_props(<>, table, rest) end defp encode_props(acc, table, [type | rest]) when type == :write_actions or type == :write_actions_miss or type == :apply_actions or type == :apply_actions_miss do type_int = Openflow.Enums.to_int(type, :table_feature_prop_type) actions_bin = encode_actions("", Map.get(table, type)) length = @prop_header_length + byte_size(actions_bin) pad_length = Openflow.Utils.pad_length(length, 8) body = <> encode_props(<>, table, rest) end defp encode_props(acc, table, [type | rest]) when type == :match or type == :wildcards or type == :write_setfield or type == :write_setfield_miss or type == :apply_setfield or type == :apply_setfield_miss do type_int = Openflow.Enums.to_int(type, :table_feature_prop_type) matches_bin = encode_matches("", Map.get(table, type)) length = @prop_header_length + byte_size(matches_bin) pad_length = Openflow.Utils.pad_length(length, 8) body = <> encode_props(<>, table, rest) end defp encode_props(acc, table, [_type | rest]) do encode_props(acc, table, rest) end defp decode_instructions(acc, ""), do: Enum.reverse(acc) defp decode_instructions(acc, <<0xFFFF::16, _::16, exp_id::32, rest::bytes>>) do decode_instructions([Openflow.Instruction.Experimenter.new(exp_id) | acc], rest) end defp decode_instructions(acc, <>) do instruction = Openflow.Enums.to_atom(type_int, :instruction_type) decode_instructions([instruction | acc], rest) end defp encode_instructions(acc, []), do: acc defp encode_instructions(acc, [%Openflow.Instruction.Experimenter{exp_id: exp_id} | rest]) do encode_instructions(<>, rest) end defp encode_instructions(acc, [type | rest]) do type_int = Openflow.Enums.to_int(type, :instruction_type) encode_instructions(<>, rest) end defp decode_actions(acc, ""), do: Enum.reverse(acc) defp decode_actions(acc, <<0xFFFF::16, _::16, exp_id::32, rest::bytes>>) do decode_actions([Openflow.Action.Experimenter.new(exp_id) | acc], rest) end defp decode_actions(acc, <>) do action = Openflow.Enums.to_atom(type_int, :action_type) decode_actions([action | acc], rest) end defp encode_actions(acc, []), do: acc defp encode_actions(acc, [%Openflow.Action.Experimenter{exp_id: exp_id} | rest]) do encode_actions(<>, rest) end defp encode_actions(acc, [type | rest]) do type_int = Openflow.Enums.to_int(type, :action_type) encode_actions(<>, rest) end defp decode_matches(acc, ""), do: Enum.reverse(acc) defp decode_matches(acc, binary) do length = Openflow.Match.header_size(binary) <> = binary field = Openflow.Match.codec_header(header_bin) decode_matches([field | acc], rest) end defp encode_matches(acc, []), do: acc defp encode_matches(acc, [field | rest]) do header_bin = Openflow.Match.codec_header(field) encode_matches(<>, rest) end end