| 1 |
|
%% Copyright (c) 2022 Peter Morgan <peter.james.morgan@gmail.com> |
| 2 |
|
%% |
| 3 |
|
%% Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 |
|
%% you may not use this file except in compliance with the License. |
| 5 |
|
%% You may obtain a copy of the License at |
| 6 |
|
%% |
| 7 |
|
%% http://www.apache.org/licenses/LICENSE-2.0 |
| 8 |
|
%% |
| 9 |
|
%% Unless required by applicable law or agreed to in writing, software |
| 10 |
|
%% distributed under the License is distributed on an "AS IS" BASIS, |
| 11 |
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 |
|
%% See the License for the specific language governing permissions and |
| 13 |
|
%% limitations under the License. |
| 14 |
|
|
| 15 |
|
|
| 16 |
|
-module(mcd_protocol). |
| 17 |
|
|
| 18 |
|
|
| 19 |
|
-export([decode/1]). |
| 20 |
|
-export([encode/1]). |
| 21 |
|
-export([reply_expected/1]). |
| 22 |
|
-import(mcd_util, [split/1]). |
| 23 |
|
-include("mcd.hrl"). |
| 24 |
|
-include_lib("kernel/include/logger.hrl"). |
| 25 |
|
|
| 26 |
|
|
| 27 |
|
-callback init([]) -> {ok, callback_data()}. |
| 28 |
|
|
| 29 |
|
-type callback_data() :: any(). |
| 30 |
|
|
| 31 |
|
-callback recv(recv_request()) -> recv_response(). |
| 32 |
|
|
| 33 |
|
-type recv_request() :: #{message := mcd:protocol(), |
| 34 |
|
data := callback_data()}. |
| 35 |
|
|
| 36 |
|
|
| 37 |
|
-type recv_response() :: {continue, continue_response()} |
| 38 |
|
| {continue, [continue_response()]} |
| 39 |
|
| {stop, any()} |
| 40 |
|
| stop. |
| 41 |
|
|
| 42 |
|
-type continue_response() :: {encode, mcd:protocol()} |
| 43 |
|
| {expire, #{key := binary(), seconds := integer()}}. |
| 44 |
|
|
| 45 |
|
-callback expire(expire_request()) -> expire_response(). |
| 46 |
|
|
| 47 |
|
-type expire_request() :: #{key := binary(), data := callback_data()}. |
| 48 |
|
|
| 49 |
|
-type expire_response() :: ok |
| 50 |
|
| {stop, any()} |
| 51 |
|
| stop. |
| 52 |
|
|
| 53 |
|
-callback flush_all(flush_all_request()) -> flush_all_response(). |
| 54 |
|
|
| 55 |
|
-type flush_all_request() :: #{data := callback_data()}. |
| 56 |
|
|
| 57 |
|
-type flush_all_response() :: ok |
| 58 |
|
| {stop, any()} |
| 59 |
|
| stop. |
| 60 |
|
|
| 61 |
|
-spec decode(binary()) -> {mcd:protocol(), binary()} | partial | {error, atom()}. |
| 62 |
|
|
| 63 |
|
decode(<<"STAT ", Remainder/bytes>>) -> |
| 64 |
:-( |
mcd_protocol_text:decode(stat, Remainder); |
| 65 |
|
|
| 66 |
|
decode(<<"stats", Remainder/bytes>>) -> |
| 67 |
:-( |
mcd_protocol_text:decode(stats, Remainder); |
| 68 |
|
|
| 69 |
|
decode(<<"stat", Remainder/bytes>>) -> |
| 70 |
:-( |
mcd_protocol_text:decode(stat, Remainder); |
| 71 |
|
|
| 72 |
|
decode(<<"quit", Remainder/bytes>>) -> |
| 73 |
:-( |
mcd_protocol_text:decode(quit, Remainder); |
| 74 |
|
|
| 75 |
|
decode(<<"flush_all", Remainder/bytes>>) -> |
| 76 |
2 |
mcd_protocol_text:decode(flush_all, Remainder); |
| 77 |
|
|
| 78 |
|
decode(<<"verbosity ", Remainder/bytes>>) -> |
| 79 |
:-( |
mcd_protocol_text:decode(verbosity, Remainder); |
| 80 |
|
|
| 81 |
|
decode(<<"set ", Remainder/bytes>>) -> |
| 82 |
39 |
mcd_protocol_text:decode(set, Remainder); |
| 83 |
|
|
| 84 |
|
decode(<<"cas ", Remainder/bytes>>) -> |
| 85 |
7 |
mcd_protocol_text:decode(cas, Remainder); |
| 86 |
|
|
| 87 |
|
decode(<<"add ", Remainder/bytes>>) -> |
| 88 |
8 |
mcd_protocol_text:decode(add, Remainder); |
| 89 |
|
|
| 90 |
|
decode(<<"replace ", Remainder/bytes>>) -> |
| 91 |
3 |
mcd_protocol_text:decode(replace, Remainder); |
| 92 |
|
|
| 93 |
|
decode(<<"append ", Remainder/bytes>>) -> |
| 94 |
1 |
mcd_protocol_text:decode(append, Remainder); |
| 95 |
|
|
| 96 |
|
decode(<<"prepend ", Remainder/bytes>>) -> |
| 97 |
1 |
mcd_protocol_text:decode(prepend, Remainder); |
| 98 |
|
|
| 99 |
|
decode(<<"get ", Remainder/bytes>>) -> |
| 100 |
35 |
mcd_protocol_text:decode(get, Remainder); |
| 101 |
|
|
| 102 |
|
decode(<<"gets ", Remainder/bytes>>) -> |
| 103 |
9 |
mcd_protocol_text:decode(gets, Remainder); |
| 104 |
|
|
| 105 |
|
decode(<<"gat ", Remainder/bytes>>) -> |
| 106 |
2 |
mcd_protocol_text:decode(gat, Remainder); |
| 107 |
|
|
| 108 |
|
decode(<<"gats ", Remainder/bytes>>) -> |
| 109 |
1 |
mcd_protocol_text:decode(gats, Remainder); |
| 110 |
|
|
| 111 |
|
decode(<<"delete ", Remainder/bytes>>) -> |
| 112 |
7 |
mcd_protocol_text:decode(delete, Remainder); |
| 113 |
|
|
| 114 |
|
decode(<<"incr ", Remainder/bytes>>) -> |
| 115 |
14 |
mcd_protocol_text:decode(incr, Remainder); |
| 116 |
|
|
| 117 |
|
decode(<<"decr ", Remainder/bytes>>) -> |
| 118 |
9 |
mcd_protocol_text:decode(decr, Remainder); |
| 119 |
|
|
| 120 |
|
decode(<<"touch ", Remainder/bytes>>) -> |
| 121 |
1 |
mcd_protocol_text:decode(touch, Remainder); |
| 122 |
|
|
| 123 |
|
decode(<<"VALUE ", Remainder/bytes>>) -> |
| 124 |
42 |
mcd_protocol_text:decode(value, Remainder); |
| 125 |
|
|
| 126 |
|
decode(<<"CLIENT_ERROR ", Remainder/bytes>>) -> |
| 127 |
4 |
mcd_protocol_text:decode(client_error, Remainder); |
| 128 |
|
|
| 129 |
|
decode(<<"SERVER_ERROR ", Remainder/bytes>>) -> |
| 130 |
:-( |
mcd_protocol_text:decode(server_error, Remainder); |
| 131 |
|
|
| 132 |
|
decode(<<"OK\r\n", Remainder/bytes>>) -> |
| 133 |
2 |
{#{command => ok}, Remainder}; |
| 134 |
|
|
| 135 |
|
decode(<<"ERROR\r\n", Remainder/bytes>>) -> |
| 136 |
:-( |
{#{command => error}, Remainder}; |
| 137 |
|
|
| 138 |
|
decode(<<"TOUCHED\r\n", Remainder/bytes>>) -> |
| 139 |
1 |
{#{command => touched}, Remainder}; |
| 140 |
|
|
| 141 |
|
decode(<<"STORED\r\n", Remainder/bytes>>) -> |
| 142 |
39 |
{#{command => stored}, Remainder}; |
| 143 |
|
|
| 144 |
|
decode(<<"NOT_STORED\r\n", Remainder/bytes>>) -> |
| 145 |
2 |
{#{command => not_stored}, Remainder}; |
| 146 |
|
|
| 147 |
|
decode(<<"EXISTS\r\n", Remainder/bytes>>) -> |
| 148 |
3 |
{#{command => exists}, Remainder}; |
| 149 |
|
|
| 150 |
|
decode(<<"NOT_FOUND\r\n", Remainder/bytes>>) -> |
| 151 |
4 |
{#{command => not_found}, Remainder}; |
| 152 |
|
|
| 153 |
|
decode(<<"END\r\n", Remainder/bytes>>) -> |
| 154 |
47 |
{#{command => 'end'}, Remainder}; |
| 155 |
|
|
| 156 |
|
decode(<<"DELETED\r\n", Remainder/bytes>>) -> |
| 157 |
4 |
{#{command => deleted}, Remainder}; |
| 158 |
|
|
| 159 |
|
decode(<<"ma ", Remainder/bytes>>) -> |
| 160 |
16 |
mcd_protocol_meta:decode(arithmetic, Remainder); |
| 161 |
|
|
| 162 |
|
decode(<<"me ", Remainder/bytes>>) -> |
| 163 |
:-( |
mcd_protocol_meta:decode(debug, Remainder); |
| 164 |
|
|
| 165 |
|
decode(<<"ME ", Remainder/bytes>>) -> |
| 166 |
:-( |
mcd_protocol_meta:decode(debug_reply, Remainder); |
| 167 |
|
|
| 168 |
|
decode(<<"mg ", Remainder/bytes>>) -> |
| 169 |
11 |
mcd_protocol_meta:decode(get, Remainder); |
| 170 |
|
|
| 171 |
|
decode(<<"mn", Remainder/bytes>>) -> |
| 172 |
2 |
mcd_protocol_meta:decode(no_op, Remainder); |
| 173 |
|
|
| 174 |
|
decode(<<"ms ", Remainder/bytes>>) -> |
| 175 |
19 |
mcd_protocol_meta:decode(set, Remainder); |
| 176 |
|
|
| 177 |
|
decode(<<"md ", Remainder/bytes>>) -> |
| 178 |
2 |
mcd_protocol_meta:decode(delete, Remainder); |
| 179 |
|
|
| 180 |
|
decode(<<"VA ", Remainder/bytes>>) -> |
| 181 |
18 |
mcd_protocol_meta:decode(value, Remainder); |
| 182 |
|
|
| 183 |
|
decode(<<"HD", Remainder/bytes>>) -> |
| 184 |
20 |
mcd_protocol_meta:decode(head, Remainder); |
| 185 |
|
|
| 186 |
|
decode(<<"NS", Remainder/bytes>>) -> |
| 187 |
2 |
mcd_protocol_meta:decode(not_stored, Remainder); |
| 188 |
|
|
| 189 |
|
decode(<<"EX", Remainder/bytes>>) -> |
| 190 |
3 |
mcd_protocol_meta:decode(exists, Remainder); |
| 191 |
|
|
| 192 |
|
decode(<<"NF", Remainder/bytes>>) -> |
| 193 |
2 |
mcd_protocol_meta:decode(not_found, Remainder); |
| 194 |
|
|
| 195 |
|
decode(<<"MN", Remainder/bytes>>) -> |
| 196 |
2 |
mcd_protocol_meta:decode(no_op_reply, Remainder); |
| 197 |
|
|
| 198 |
|
decode(<<"EN\r\n", Remainder/bytes>>) -> |
| 199 |
:-( |
{#{meta => miss}, Remainder}; |
| 200 |
|
|
| 201 |
|
decode(<<Magic:8, |
| 202 |
|
_:8, |
| 203 |
|
_:16, |
| 204 |
|
_:8, |
| 205 |
|
?RAW:8, |
| 206 |
|
_:16, |
| 207 |
|
TotalBodyLength:32, |
| 208 |
|
_:32, |
| 209 |
|
_:64, |
| 210 |
|
_:TotalBodyLength/bytes, |
| 211 |
|
_/bytes>> = Arg) when Magic == ?REQUEST; Magic == ?RESPONSE -> |
| 212 |
30 |
mcd_protocol_binary:decode(Arg); |
| 213 |
|
|
| 214 |
|
decode(Arg) -> |
| 215 |
13 |
[CommandLine, _] = split(Arg), |
| 216 |
13 |
case re:run(CommandLine, "\\d+") of |
| 217 |
|
{match, _} -> |
| 218 |
13 |
mcd_protocol_text:decode(incrdecr, Arg); |
| 219 |
|
|
| 220 |
|
nomatch -> |
| 221 |
:-( |
?LOG_ERROR(#{arg => Arg}), |
| 222 |
:-( |
error(client_error) |
| 223 |
|
end. |
| 224 |
|
|
| 225 |
|
|
| 226 |
|
encode(L) when is_list(L) -> |
| 227 |
184 |
lists:map(fun encode/1, L); |
| 228 |
|
|
| 229 |
|
encode(#{meta := _} = Arg) -> |
| 230 |
97 |
mcd_protocol_meta:encode(Arg); |
| 231 |
|
|
| 232 |
|
encode(#{command := _} = Arg) -> |
| 233 |
300 |
mcd_protocol_text:encode(Arg); |
| 234 |
|
|
| 235 |
|
encode(#{header := #{opcode := _}} = Arg) -> |
| 236 |
30 |
mcd_protocol_binary:encode(Arg). |
| 237 |
|
|
| 238 |
|
|
| 239 |
|
reply_expected(L) when is_list(L) -> |
| 240 |
176 |
lists:filter(fun ?FUNCTION_NAME/1, L); |
| 241 |
|
|
| 242 |
|
reply_expected(#{command := _, noreply := Noreply}) -> |
| 243 |
92 |
not(Noreply); |
| 244 |
|
|
| 245 |
|
reply_expected(#{meta := _, flags := Flags}) -> |
| 246 |
48 |
not(lists:member(noreply, Flags)); |
| 247 |
|
|
| 248 |
|
reply_expected(#{}) -> |
| 249 |
64 |
true. |