# https://github.com/memcached/old-wiki/blob/master/MemcacheBinaryProtocol.wiki defmodule Memcache.Protocol do @moduledoc false import Memcache.BinaryUtils alias Memcache.BinaryUtils.Header def to_binary(:QUIT) do bcat([ request, opb(:QUIT), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]) end def to_binary(:NOOP) do bcat([ request, opb(:NOOP), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]) end def to_binary(:VERSION) do bcat([ request, opb(:VERSION), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]) end def to_binary(:STAT) do bcat([ request, opb(:STAT), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]) end def to_binary(:AUTH_LIST) do bcat([ request, opb(:AUTH_LIST), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]) end def to_binary(command) do to_binary(command, 0) end def to_binary(:NOOP, opaque) do [ bcat([ request, opb(:NOOP), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>]), << opaque :: size(32) >>, << 0x00 :: size(64) >> ] end def to_binary(:STAT, key) do [ bcat([ request, opb(:STAT)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, bcat([ opaque, << 0x00 :: size(64) >>]), key ] end def to_binary(:GET, key) do [ bcat([ request, opb(:GET)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, bcat([ opaque, << 0x00 :: size(64) >>]), key ] end def to_binary(:GETK, key) do [ bcat([ request, opb(:GETK)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, bcat([ opaque, << 0x00 :: size(64) >>]), key ] end def to_binary(:FLUSH, 0) do bcat([ request, opb(:FLUSH), << 0x00 :: size(16) >>, << 0x00 >>, datatype, reserved, << 0x00 :: size(32) >>, opaque, << 0x00 :: size(64) >>]) end def to_binary(:FLUSH, expiry) do [ bcat([ request, opb(:FLUSH), << 0x00 :: size(16) >>, << 0x04 >>, datatype, reserved, << 0x04 :: size(32) >>, opaque, << 0x00 :: size(64) >> ]), << expiry :: size(32) >> ] end def to_binary(:AUTH_START, key) do [ bcat([ request, opb(:AUTH_START)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, opaque, << 0x00 :: size(64) >>, key ] end def to_binary(command, key) do to_binary(command, key, 0) end def to_binary(:AUTH_START, key, value) do [ bcat([ request, opb(:AUTH_START)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) + IO.iodata_length(value) :: size(32) >>, opaque, << 0x00 :: size(64) >>, key, value ] end def to_binary(:GETQ, id, key) do [ bcat([ request, opb(:GETQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, << id :: size(32) >>, << 0x00 :: size(64) >>, key ] end def to_binary(:GETKQ, id, key) do [ bcat([ request, opb(:GETKQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) :: size(32) >>, << id :: size(32) >>, << 0x00 :: size(64) >>, key ] end def to_binary(:DELETE, key, cas) do [ bcat([ request, opb(:DELETE)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) :: size(32) >>, opaque, << cas :: size(64) >> , key ] end def to_binary(:DELETEQ, id, key) do [ bcat([ request, opb(:DELETEQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) :: size(32) >>, << id :: size(32) >>, << 0x00 :: size(64) >>, key ] end def to_binary(command, key, value) do to_binary(command, key, value, 0) end def to_binary(:APPEND, key, value, cas) do [ bcat([ request, opb(:APPEND)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) + IO.iodata_length(value) :: size(32) >>, opaque, << cas :: size(64) >>, key, value ] end def to_binary(:PREPEND, key, value, cas) do [ bcat([ request, opb(:PREPEND)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) + IO.iodata_length(value) :: size(32) >>, opaque, << cas :: size(64) >>, key, value ] end def to_binary(:APPENDQ, id, key, value) do [ bcat([ request, opb(:APPENDQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) + IO.iodata_length(value) :: size(32) >>, << id :: size(32) >>, << 0x00 :: size(64) >>, key, value ] end def to_binary(:PREPENDQ, id, key, value) do [ bcat([ request, opb(:PREPENDQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x00 >>, datatype, reserved]), << byte_size(key) + IO.iodata_length(value) :: size(32) >>, << id :: size(32) >>, << 0x00 :: size(64) >>, key, value ] end def to_binary(command, key, value, cas) do to_binary(command, key, value, cas, 0, 0) end def to_binary(command, key, value, cas, flag) do to_binary(command, key, value, cas, flag, 0) end def to_binary(:INCREMENT, key, delta, initial, cas, expiry) do [ bcat([ request, opb(:INCREMENT)]), << byte_size(key) :: size(16) >>, bcat([<< 0x14 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 20 :: size(32) >>, opaque, << cas :: size(64) >>, << delta :: size(64) >>, << initial :: size(64) >>, << expiry :: size(32) >>, key ] end def to_binary(:DECREMENT, key, delta, initial, cas, expiry) do [ bcat([ request, opb(:DECREMENT)]), << byte_size(key) :: size(16) >>, bcat([<< 0x14 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 20 :: size(32) >>, opaque, << cas :: size(64) >>, << delta :: size(64) >>, << initial :: size(64) >>, << expiry :: size(32) >>, key ] end def to_binary(:SET, key, value, cas, expiry, flag) do [ bcat([ request, opb(:SET)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, opaque, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(:ADD, key, value, expiry, cas, flag) do [ bcat([ request, opb(:ADD)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, opaque, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(:REPLACE, key, value, cas, expiry, flag) do [ bcat([ request, opb(:REPLACE)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, opaque, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(command, id, key, value, cas, flag) do to_binary(command, id, key, value, cas, flag, 0) end def to_binary(:SETQ, id, key, value, cas, flag, expiry) do [ bcat([ request, opb(:SETQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, << id :: size(32) >>, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(:ADDQ, id, key, value, cas, flag, expiry) do [ bcat([ request, opb(:ADDQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, << id :: size(32) >>, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(:REPLACEQ, id, key, value, cas, flag, expiry) do [ bcat([ request, opb(:REPLACEQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x08 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 8 + IO.iodata_length(value) :: size(32) >>, << id :: size(32) >>, << cas :: size(64) >>, << flag :: size(32) >>, << expiry :: size(32) >>, key, value ] end def to_binary(:INCREMENTQ, id, key, delta, initial, cas, expiry) do [ bcat([ request, opb(:INCREMENTQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x14 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 20 :: size(32) >>, << id :: size(32) >>, << cas :: size(64) >>, << delta :: size(64) >>, << initial :: size(64) >>, << expiry :: size(32) >>, key ] end def to_binary(:DECREMENTQ, id, key, delta, initial, cas, expiry) do [ bcat([ request, opb(:DECREMENTQ)]), << byte_size(key) :: size(16) >>, bcat([<< 0x14 >>, << 0x00 >>, << 0x0000 :: size(16) >>]), << byte_size(key) + 20 :: size(32) >>, << id :: size(32) >>, << cas :: size(64) >>, << delta :: size(64) >>, << initial :: size(64) >>, << expiry :: size(32) >>, key ] end def parse_header(<< 0x81 :: size(8), opcode :: size(8), key_length :: size(16), extra_length :: size(8), data_type :: size(8), status :: size(16), total_body_length :: size(32), opaque :: size(32), cas :: size(64) >>) do %Header{ opcode: opcode, key_length: key_length, extra_length: extra_length, data_type: data_type, status: status, total_body_length: total_body_length, opaque: opaque, cas: cas } end def total_body_size(%Header{total_body_length: size}) do size end def parse_body(%Header{ status: 0x0000, opcode: op(:GET), extra_length: extra_length, total_body_length: total_body_length }, rest) do value_size = (total_body_length - extra_length) << _extra :: binary-size(extra_length), value :: binary-size(value_size) >> = rest { :ok, value } end def parse_body(%Header{ status: 0x0000, opcode: op(:GETQ), extra_length: extra_length, total_body_length: total_body_length, opaque: opaque }, rest) do value_size = (total_body_length - extra_length) << _extra :: binary-size(extra_length), value :: binary-size(value_size) >> = rest { opaque, { :ok, value } } end def parse_body(%Header{ status: 0x0000, opcode: op(:GETK), extra_length: extra_length, key_length: key_length, total_body_length: total_body_length }, rest) do value_size = (total_body_length - extra_length - key_length) << _extra :: binary-size(extra_length), key :: binary-size(key_length), value :: binary-size(value_size) >> = rest { :ok, key, value } end def parse_body(%Header{ status: 0x0000, opcode: op(:GETKQ), extra_length: extra_length, key_length: key_length, total_body_length: total_body_length, opaque: opaque }, rest) do value_size = (total_body_length - extra_length - key_length) << _extra :: binary-size(extra_length), key :: binary-size(key_length), value :: binary-size(value_size) >> = rest { opaque, { :ok, key, value }} end def parse_body(%Header{ status: 0x0000, opcode: op(:VERSION) }, rest) do { :ok, rest } end def parse_body(%Header{ status: 0x0000, opcode: op(:AUTH_LIST) }, rest) do { :ok, rest } end def parse_body(%Header{ status: 0x0000, opcode: op(:INCREMENT) }, rest) do << value :: size(64) >> = rest { :ok, value } end def parse_body(%Header{ status: 0x0000, opcode: op(:INCREMENTQ), opaque: opaque }, rest) do << value :: size(64) >> = rest { opaque, { :ok, value }} end def parse_body(%Header{ status: 0x0000, opcode: op(:DECREMENT) }, rest) do << value :: size(64) >> = rest { :ok, value } end def parse_body(%Header{ status: 0x0000, opcode: op(:DECREMENTQ), opaque: opaque }, rest) do << value :: size(64) >> = rest { opaque, { :ok, value }} end def parse_body(%Header{ status: 0x0000, opcode: op(:AUTH_START)}, _rest) do { :ok } end def parse_body(%Header{ status: 0x0000, opcode: op(:STAT), key_length: 0, total_body_length: 0 }, _rest) do { :ok, :done } end def parse_body(%Header{ status: 0x0000, opcode: op(:STAT), key_length: key_length, total_body_length: total_body_length }, rest) do value_size = (total_body_length - key_length) << key :: binary-size(key_length), value :: binary-size(value_size) >> = rest { :ok, key, value } end def parse_body(%Header{ status: 0x0021 }, body) do { :error, :auth_step, body } end defparse_empty(:SET) defparse_empty(:ADD) defparse_empty(:REPLACE) defparse_empty(:DELETE) defparse_empty(:QUIT) defparse_empty(:FLUSH) defparse_empty(:NOOP) defparse_empty(:APPEND) defparse_empty(:PREPEND) defparse_error(0x0001, "Key not found") defparse_error(0x0002, "Key exists") defparse_error(0x0003, "Value too large") defparse_error(0x0004, "Invalid arguments") defparse_error(0x0005, "Item not stored") defparse_error(0x0006, "Incr/Decr on non-numeric value") defparse_error(0x0008, "Authentication Error") defparse_error(0x0009, "Authentication Continue") defparse_error(0x0020, "Authentication required / Not Successful") defparse_error(0x0081, "Unknown command") defparse_error(0x0082, "Out of memory") def quiet_response(:GETQ), do: { :ok, "Key not found" } def quiet_response(:GETKQ), do: { :ok, "Key not found" } def quiet_response(:SETQ), do: { :ok } def quiet_response(:ADDQ), do: { :ok } def quiet_response(:DELETEQ), do: { :ok } def quiet_response(:REPLACEQ), do: { :ok } def quiet_response(:INCREMENTQ), do: { :ok } def quiet_response(:DECREMENTQ), do: { :ok } def quiet_response(:APPENDQ), do: { :ok } def quiet_response(:PREPENDQ), do: { :ok } end