1defmodule Mariaex.Coder do 2 @moduledoc """ 3 Declarative generator for MySQL protocol messages, which can generate based on declarative description 4 decoder and encoder. 5 6 Example: 7 8 defcoder :text_cmd do 9 command 1 10 statement :string_eof 11 end 12 13 Will generate 2 functions: 14 15 __encode__({:text_cmd, 0x0e, "test"}) # => <<14, 116, 101, 115, 116>> 16 17 __decode__(:text_cmd, <<14, 116, 101, 115, 116>>) # => {:text_cmd, 14, "test"} 18 19 Additionally it generates record, like `Record.record(:text_cmd, [:command, :statement])`, 20 so that you can use it to create commands or access information in it. 21 22 Example would be: `text_cmd(command: 14, statement: "test")` 23 24 Check `Mariaex.Messages` for more examples. 25 26 For now, there is possible to insert custom functions for decoding of data. Example is in handshake 27 command: 28 29 See definition and implementation: 30 31 `auth_plugin_data2: {__MODULE__, auth_plugin_data2}` 32 33 It is used only for decoding, but it may change in the future for encoding. 34 """ 35 defmacro __using__(_opts) do 36 quote do 37 import Mariaex.Coder, only: [defcoder: 2] 38 import Record, only: [defrecord: 2] 39 import Mariaex.Coder.Utils 40 41 @before_compile unquote(__MODULE__) 42 Module.register_attribute(__MODULE__, :decoders, accumulate: true) 43 Module.register_attribute(__MODULE__, :encoders, accumulate: true) 44 end 45 end 46 47 defmacro __before_compile__(env) do 48 decoders = Enum.reverse Module.get_attribute(env.module, :decoders) 49 encoders = Enum.reverse Module.get_attribute(env.module, :encoders) 50 [for {type, function} <- decoders do 51 quote do 52 def __decode__(unquote(type), body), do: unquote(function)(body) 53 end 54 end, 55 for {type, function} <- encoders do 56 quote do 57 def __encode__(unquote(type)() = rec), do: unquote(function)(rec) 58 end 59 end] 60 end 61 62 defmacro defcoder(name, [do: spec]) do 63 spec = case spec do 64 {:__block__, _meta, spec} -> spec 65 spec -> [spec] 66 end 67 keys = for {key, _, _} <- spec, key != :_, do: key 68 decoder = split_to_stages(spec) |> gen_stages(name, keys) 69 encoder = gen_encoder(name, spec, keys) 70 quote do 71 defrecord unquote(name), unquote(keys) 72 unquote(decoder) 73 unquote(encoder) 74 end 75 end 76 77 def gen_encoder(name, spec, keys) do 78 function = ("encode_" <> Atom.to_string(name)) |> String.to_atom 79 quote do 80 Module.put_attribute __MODULE__, :encoders, {unquote(name), unquote(function)} 81 def unquote(function)(unquote(name)(unquote(for key <- keys, do: {key, Macro.var(key, nil)}))) do 82 unquote({:<<>>, [], Enum.flat_map(spec, &match(&1, :encode))}) 83 end 84 end 85 end 86 87 @empty_stage %{head: [], body: nil} 88 89 defp split_to_stages(spec) do 90 {last, other} = Enum.reduce(spec, {@empty_stage, []}, fn(kv = {_key, _, [value | _]}, {actual = %{head: head, body: _body}, all}) -> 91 cond do 92 is_integer(value) -> 93 {%{actual | head: [kv | head]}, all} 94 true -> 95 {@empty_stage, [%{actual | head: Enum.reverse([:next | head]), body: kv} | all]} 96 end 97 end) 98 case last do 99 %{head: [], body: nil} -> other 100 _ -> [%{last | head: Enum.reverse(last.head) } | other] 101 end |> Enum.reverse 102 end 103 104 defp gen_stages(allspec, name, keys) do 105 matches = gen_matches(allspec, keys) 106 function = ("decode_" <> Atom.to_string(name)) |> String.to_atom 107 quote do 108 Module.put_attribute __MODULE__, :decoders, {unquote(name), unquote(function)} 109 def unquote(function)(next) do 110 unquote_splicing(matches) 111 unquote(name)(unquote(for key <- keys, do: {key, Macro.var(key, nil)})) 112 end 113 end 114 end 115 116 defp gen_matches(allspec, keys) do 117 for spec <- allspec do 118 body = gen_body(spec[:body], keys) 119 quoted_head = case spec[:head] do 120 [:next] -> 121 [] 122 head -> 123 binary_match = {:<<>>, [], Enum.flat_map(head, &match(&1, :decode))} 124 [(quote do: unquote(binary_match) = next)] 125 end 126 quoted_head ++ [body] 127 end |> List.flatten 128 end 129 130 defp gen_body({key, _, [:length_string]}, _) do 131 quote do 132 <<length :: size(8)-little, unquote(Macro.var(key, nil)) :: size(length)-binary, next :: binary>> = next 133 end 134 end 135 136 defp gen_body({key, _, [{module, function}]}, _) do 137 quote do: {unquote(Macro.var(key, nil)), next} = apply(unquote(module), unquote(function), [next]) 138 end 139 140 defp gen_body({key, _, [:length_encoded_integer]}, _) do 141 quote do: {unquote(Macro.var(key, nil)), next} = length_encoded_integer(next) 142 end 143 144 defp gen_body({key, _, [:length_encoded_string]}, _) do 145 quote do: {unquote(Macro.var(key, nil)), next} = length_encoded_string(next) 146 end 147 148 defp gen_body({key, _, [:length_encoded_string, :until_eof]}, _) do 149 quote do: unquote(Macro.var(key, nil)) = length_encoded_string_eof(next) 150 end 151 152 defp gen_body({key, _, [:string]}, _) do 153 quote do: [unquote(Macro.var(key, nil)), next] = :binary.split(next, <<0>>) 154 end 155 156 defp gen_body({key, _, [:string_eof]}, _) do 157 quote do: unquote(Macro.var(key, nil)) = next 158 end 159 160 defp gen_body({key, _, [function]}, _keys) do 161 quote do 162 size = unquote(function) * 8 163 <<unquote(Macro.var(key, nil)) :: size(size), next :: binary>> = next 164 end 165 end 166 167 defp gen_body({key, _, [function, :string]}, _keys) do 168 quote do 169 size = unquote(function) 170 <<unquote(Macro.var(key, nil)) :: size(size)-binary, next :: binary>> = next 171 end 172 end 173 defp gen_body(nil, _keys) do 174 [] 175 end 176 177 defp match({:_, _, [length]}, _) when is_integer(length) do 178 [quote do: 0 :: unquote(length)*8] 179 end 180 defp match({key, _, [length]}, _) when is_integer(length) do 181 [quote do: unquote(Macro.var(key, nil)) :: size(unquote(length*8))-little] 182 end 183 defp match({key, _, [length, :string]}, _) do 184 [quote do: unquote(Macro.var(key, nil)) :: size(unquote(length))-binary] 185 end 186 defp match(:next, _) do 187 [quote do: next :: binary] 188 end 189 defp match({key, _, [:string]}, _) do 190 [(quote do: unquote(Macro.var(key, nil)) :: binary), 191 (quote do: 0 :: 8)] 192 end 193 defp match({key, _, [:length_string]}, :encode) do 194 [(quote do: byte_size(unquote(Macro.var(key, nil))) :: 8 ), 195 (quote do: unquote(Macro.var(key, nil)) :: binary)] 196 end 197 defp match({key, _, [:string_eof]}, :encode) do 198 [(quote do: unquote(Macro.var(key, nil)) :: binary)] 199 end 200 # this clauses are wrong, because it is imposible to generate this kind of integer in a binary match 201 defp match({key, _, [:length_encoded_integer]}, :encode) do 202 [(quote do: unquote(Macro.var(key, nil)) :: integer)] 203 end 204 defp match({key, _, [:length_encoded_string | _]}, :encode) do 205 [(quote do: unquote(Macro.var(key, nil)) :: binary)] 206 end 207 # All custom implementations are ignored yet 208 defp match({key, _, [{_module, _function}]}, :encode) do 209 [(quote do: unquote(Macro.var(key, nil)) :: binary)] 210 end 211 212 defmodule Utils do 213 def length_encoded_string(bin) do 214 {length, next} = length_encoded_integer(bin) 215 << string :: size(length)-binary, next :: binary >> = next 216 {string, next} 217 end 218 219 def length_encoded_string_eof(bin, acc \\ []) do 220 case length_encoded_string(bin) do 221 {value, ""} -> 222 Enum.reverse([value | acc]) 223 {value, rest} -> 224 length_encoded_string_eof(rest, [value | acc]) 225 end 226 end 227 228 def length_encoded_integer(bin) do 229 case bin do 230 << value :: 8, rest :: binary >> when value <= 250 -> {value, rest} 231 << 252 :: 8, value :: 16-little, rest :: bits >> -> {value, rest} 232 << 253 :: 8, value :: 24-little, rest :: bits >> -> {value, rest} 233 << 254 :: 8, value :: 64-little, rest :: bits >> -> {value, rest} 234 end 235 end 236 237 def to_length_encoded_integer(int) do 238 case int do 239 int when int <= 250 -> << int :: 8 >> 240 int when int <= 65535 -> << 252 :: 8, int :: 16-little >> 241 int when int <= 16777215 -> << 253 :: 8, int :: 24-little >> 242 int -> << 254 :: 8, int :: 64-little >> 243 end 244 end 245 end 246end 247