mysql_protocol.erl 74 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692
  1. %% MySQL/OTP – MySQL client library for Erlang/OTP
  2. %% Copyright (C) 2014 Viktor Söderqvist
  3. %% 2017 Piotr Nosek, Michal Slaski
  4. %%
  5. %% This file is part of MySQL/OTP.
  6. %%
  7. %% MySQL/OTP is free software: you can redistribute it and/or modify it under
  8. %% the terms of the GNU Lesser General Public License as published by the Free
  9. %% Software Foundation, either version 3 of the License, or (at your option)
  10. %% any later version.
  11. %%
  12. %% This program is distributed in the hope that it will be useful, but WITHOUT
  13. %% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14. %% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  15. %% more details.
  16. %%
  17. %% You should have received a copy of the GNU Lesser General Public License
  18. %% along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. %% @doc This module implements parts of the MySQL client/server protocol.
  20. %%
  21. %% The protocol is described in the document "MySQL Internals" which can be
  22. %% found under "MySQL Documentation: Expert Guides" on http://dev.mysql.com/.
  23. %%
  24. %% TCP communication is not handled in this module. Most of the public functions
  25. %% take funs for data communitaction as parameters.
  26. %% @private
  27. -module(mysql_protocol).
  28. -export([handshake/8, change_user/8, quit/2, ping/2,
  29. query/4, query/5, fetch_query_response/3,
  30. fetch_query_response/4, prepare/3, unprepare/3,
  31. execute/5, execute/6, fetch_execute_response/3,
  32. fetch_execute_response/4, reset_connnection/2,
  33. valid_params/1]).
  34. -type query_filtermap() :: no_filtermap_fun
  35. | fun(([term()]) -> query_filtermap_res())
  36. | fun(([term()], [term()]) -> query_filtermap_res()).
  37. -type query_filtermap_res() :: boolean() | {true, term()}.
  38. -type auth_more_data() :: fast_auth_completed
  39. | full_auth_requested
  40. | {public_key, term()}.
  41. %% How much data do we want per packet?
  42. -define(MAX_BYTES_PER_PACKET, 16#1000000).
  43. -include_lib("public_key/include/public_key.hrl").
  44. -include("records.hrl").
  45. -include("protocol.hrl").
  46. -include("server_status.hrl").
  47. %% Macros for pattern matching on packets.
  48. -define(ok_pattern, <<?OK, _/binary>>).
  49. -define(error_pattern, <<?ERROR, _/binary>>).
  50. -define(eof_pattern, <<?EOF, _:4/binary>>).
  51. %% Macros for auth methods.
  52. -define(authmethod_none, <<>>).
  53. -define(authmethod_mysql_native_password, <<"mysql_native_password">>).
  54. -define(authmethod_sha256_password, <<"sha256_password">>).
  55. -define(authmethod_caching_sha2_password, <<"caching_sha2_password">>).
  56. %% @doc Performs a handshake using the supplied socket and socket module for
  57. %% communication. Returns an ok or an error record. Raises errors when various
  58. %% unimplemented features are requested.
  59. -spec handshake(Host :: inet:socket_address() | inet:hostname(),
  60. Username :: iodata(), Password :: iodata(),
  61. Database :: iodata() | undefined,
  62. SockModule :: module(), SSLOpts :: list() | undefined,
  63. Socket :: term(),
  64. SetFoundRows :: boolean()) ->
  65. {ok, #handshake{}, SockModule :: module(), Socket :: term()} |
  66. #error{}.
  67. handshake(Host, Username, Password, Database, SockModule0, SSLOpts, Socket0,
  68. SetFoundRows) ->
  69. SeqNum0 = 0,
  70. {ok, HandshakePacket, SeqNum1} = recv_packet(SockModule0, Socket0, SeqNum0),
  71. case parse_handshake(HandshakePacket) of
  72. #handshake{} = Handshake ->
  73. {ok, SockModule, Socket, SeqNum2} =
  74. maybe_do_ssl_upgrade(Host, SockModule0, Socket0, SeqNum1, Handshake,
  75. SSLOpts, Database, SetFoundRows),
  76. Response = build_handshake_response(Handshake, Username, Password,
  77. Database, SetFoundRows),
  78. {ok, SeqNum3} = send_packet(SockModule, Socket, Response, SeqNum2),
  79. handshake_finish_or_switch_auth(Handshake, Password, SockModule, Socket,
  80. SeqNum3);
  81. #error{} = Error ->
  82. Error
  83. end.
  84. handshake_finish_or_switch_auth(Handshake, Password, SockModule, Socket, SeqNum) ->
  85. #handshake{auth_plugin_name = AuthPluginName,
  86. auth_plugin_data = AuthPluginData,
  87. server_version = ServerVersion,
  88. status = Status} = Handshake,
  89. AuthResult = auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
  90. SockModule, Socket, ServerVersion, SeqNum),
  91. case AuthResult of
  92. #ok{status = OkStatus} ->
  93. %% check status, ignoring bit 16#4000, SERVER_SESSION_STATE_CHANGED
  94. %% and bit 16#0002, SERVER_STATUS_AUTOCOMMIT.
  95. BitMask = bnot (?SERVER_SESSION_STATE_CHANGED bor ?SERVER_STATUS_AUTOCOMMIT),
  96. StatusMasked = Status band BitMask,
  97. StatusMasked = OkStatus band BitMask,
  98. {ok, Handshake, SockModule, Socket};
  99. Error ->
  100. Error
  101. end.
  102. %% Finish the authentication, or switch to another auth method.
  103. %%
  104. %% An OK Packet signals authentication success.
  105. %%
  106. %% An Error Packet signals authentication failure.
  107. %%
  108. %% If the authentication process requires more data to be exchanged between
  109. %% the server and client, this is done via More Data Packets. The formats and
  110. %% meanings of the payloads in such packets depend on the auth method.
  111. %%
  112. %% An Auth Method Switch Packet signals a request for transition to another
  113. %% auth method. The packet contains the name of the auth method to switch to,
  114. %% and new auth plugin data.
  115. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
  116. SockModule, Socket, ServerVersion, SeqNum0) ->
  117. {ok, ConfirmPacket, SeqNum1} = recv_packet(SockModule, Socket, SeqNum0),
  118. case parse_handshake_confirm(ConfirmPacket) of
  119. #ok{} = Ok ->
  120. %% Authentication success.
  121. Ok;
  122. #auth_method_switch{auth_plugin_name = SwitchAuthPluginName,
  123. auth_plugin_data = SwitchAuthPluginData} ->
  124. %% Server wants to transition to a different auth method.
  125. %% Send hash of password, calculated according to the requested auth method.
  126. %% (NOTE: Sending the password hash as a response to an auth method switch
  127. %% is the answer for both mysql_native_password and caching_sha2_password
  128. %% methods. It may be different for future other auth methods.)
  129. Hash = hash_password(SwitchAuthPluginName, Password, SwitchAuthPluginData),
  130. {ok, SeqNum2} = send_packet(SockModule, Socket, Hash, SeqNum1),
  131. auth_finish_or_switch(SwitchAuthPluginName, SwitchAuthPluginData, Password,
  132. SockModule, Socket, ServerVersion, SeqNum2);
  133. fast_auth_completed ->
  134. %% Server signals success by fast authentication (probably specific to
  135. %% the caching_sha2_password method). This will be followed by an OK Packet.
  136. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
  137. Socket, ServerVersion, SeqNum1);
  138. full_auth_requested when SockModule =:= ssl ->
  139. %% Server wants full authentication (probably specific to the
  140. %% caching_sha2_password method), and we are on a secure channel since
  141. %% our connection is through SSL. We have to reply with the null-terminated
  142. %% clear text password.
  143. Password1 = case is_binary(Password) of
  144. true -> Password;
  145. false -> iolist_to_binary(Password)
  146. end,
  147. {ok, SeqNum2} = send_packet(SockModule, Socket, <<Password1/binary, 0>>, SeqNum1),
  148. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
  149. Socket, ServerVersion, SeqNum2);
  150. full_auth_requested ->
  151. %% Server wants full authentication (probably specific to the
  152. %% caching_sha2_password method), and we are not on a secure channel.
  153. %% Since we are not implementing the client-side caching of the server's
  154. %% public key, we must ask for it by sending a single byte "2".
  155. {ok, SeqNum2} = send_packet(SockModule, Socket, <<2:8>>, SeqNum1),
  156. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
  157. Socket, ServerVersion, SeqNum2);
  158. {public_key, PubKey} ->
  159. %% Serveri has sent its public key (certainly specific to the caching_sha2_password
  160. %% method). We encrypt the password with the public key we received and send
  161. %% it back to the server.
  162. EncryptedPassword = encrypt_password(Password, AuthPluginData, PubKey,
  163. ServerVersion),
  164. {ok, SeqNum2} = send_packet(SockModule, Socket, EncryptedPassword, SeqNum1),
  165. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password, SockModule,
  166. Socket, ServerVersion, SeqNum2);
  167. Error ->
  168. %% Authentication failure.
  169. Error
  170. end.
  171. -spec quit(module(), term()) -> ok.
  172. quit(SockModule, Socket) ->
  173. {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_QUIT>>, 0),
  174. case recv_packet(SockModule, Socket, SeqNum1) of
  175. {error, closed} -> ok; %% MySQL 5.5.40 and more
  176. {ok, ?ok_pattern, _SeqNum2} -> ok %% Some older MySQL versions?
  177. end.
  178. -spec ping(module(), term()) -> #ok{}.
  179. ping(SockModule, Socket) ->
  180. {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_PING>>, 0),
  181. {ok, OkPacket, _SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
  182. parse_ok_packet(OkPacket).
  183. -spec query(Query :: iodata(), module(), term(), timeout()) ->
  184. {ok, [#ok{} | #resultset{} | #error{}]} | {error, timeout}.
  185. query(Query, SockModule, Socket, Timeout) ->
  186. query(Query, SockModule, Socket, no_filtermap_fun, Timeout).
  187. -spec query(Query :: iodata(), module(), term(), query_filtermap(), timeout()) ->
  188. {ok, [#ok{} | #resultset{} | #error{}]} | {error, timeout}.
  189. query(Query, SockModule, Socket, FilterMap, Timeout) ->
  190. Req = <<?COM_QUERY, (iolist_to_binary(Query))/binary>>,
  191. SeqNum0 = 0,
  192. {ok, _SeqNum1} = send_packet(SockModule, Socket, Req, SeqNum0),
  193. fetch_query_response(SockModule, Socket, FilterMap, Timeout).
  194. %% @doc This is used by query/4. If query/4 returns {error, timeout}, this
  195. %% function can be called to retry to fetch the results of the query.
  196. fetch_query_response(SockModule, Socket, Timeout) ->
  197. fetch_query_response(SockModule, Socket, no_filtermap_fun, Timeout).
  198. fetch_query_response(SockModule, Socket, FilterMap, Timeout) ->
  199. fetch_response(SockModule, Socket, Timeout, text, FilterMap, []).
  200. %% @doc Prepares a statement.
  201. -spec prepare(iodata(), module(), term()) -> #error{} | #prepared{}.
  202. prepare(Query, SockModule, Socket) ->
  203. Req = <<?COM_STMT_PREPARE, (iolist_to_binary(Query))/binary>>,
  204. {ok, SeqNum1} = send_packet(SockModule, Socket, Req, 0),
  205. {ok, Resp, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
  206. case Resp of
  207. ?error_pattern ->
  208. parse_error_packet(Resp);
  209. <<?OK,
  210. StmtId:32/little,
  211. NumColumns:16/little,
  212. NumParams:16/little,
  213. 0, %% reserved_1 -- [00] filler
  214. WarningCount:16/little>> ->
  215. %% This was the first packet.
  216. %% Now: Parameter Definition Block. The parameter definitions don't
  217. %% contain any useful data at all. They are always TYPE_VAR_STRING
  218. %% with charset 'binary' so we have to select a type ourselves for
  219. %% the parameters we have in execute/4.
  220. {_ParamDefs, SeqNum3} =
  221. fetch_column_definitions_if_any(NumParams, SockModule, Socket,
  222. SeqNum2),
  223. %% Column Definition Block. We get column definitions in execute
  224. %% too, so we don't need them here. We *could* store them to be able
  225. %% to provide the user with some info about a prepared statement.
  226. {_ColDefs, _SeqNum4} =
  227. fetch_column_definitions_if_any(NumColumns, SockModule, Socket,
  228. SeqNum3),
  229. #prepared{statement_id = StmtId,
  230. orig_query = Query,
  231. param_count = NumParams,
  232. warning_count = WarningCount}
  233. end.
  234. %% @doc Deallocates a prepared statement.
  235. -spec unprepare(#prepared{}, module(), term()) -> ok.
  236. unprepare(#prepared{statement_id = Id}, SockModule, Socket) ->
  237. {ok, _SeqNum} = send_packet(SockModule, Socket,
  238. <<?COM_STMT_CLOSE, Id:32/little>>, 0),
  239. ok.
  240. %% @doc Executes a prepared statement.
  241. -spec execute(#prepared{}, [term()], module(), term(), timeout()) ->
  242. {ok, [#ok{} | #resultset{} | #error{}]} | {error, timeout}.
  243. execute(PrepStmt, ParamValues, SockModule, Socket, Timeout) ->
  244. execute(PrepStmt, ParamValues, SockModule, Socket, no_filtermap_fun,
  245. Timeout).
  246. -spec execute(#prepared{}, [term()], module(), term(), query_filtermap(),
  247. timeout()) ->
  248. {ok, [#ok{} | #resultset{} | #error{}]} | {error, timeout}.
  249. execute(#prepared{statement_id = Id, param_count = ParamCount}, ParamValues,
  250. SockModule, Socket, FilterMap, Timeout)
  251. when ParamCount == length(ParamValues) ->
  252. %% Flags Constant Name
  253. %% 0x00 CURSOR_TYPE_NO_CURSOR
  254. %% 0x01 CURSOR_TYPE_READ_ONLY
  255. %% 0x02 CURSOR_TYPE_FOR_UPDATE
  256. %% 0x04 CURSOR_TYPE_SCROLLABLE
  257. Flags = 0,
  258. Req0 = <<?COM_STMT_EXECUTE, Id:32/little, Flags, 1:32/little>>,
  259. Req = case ParamCount of
  260. 0 ->
  261. Req0;
  262. _ ->
  263. %% We can't use the parameter types returned by the prepare call.
  264. %% They are all reported as ?TYPE_VAR_STRING with character
  265. %% set 'binary'.
  266. NullBitMap = build_null_bitmap(ParamValues),
  267. %% What does it mean to *not* bind new params? To use the same
  268. %% params as last time? Right now we always bind params each time.
  269. NewParamsBoundFlag = 1,
  270. Req1 = <<Req0/binary, NullBitMap/binary, NewParamsBoundFlag>>,
  271. %% For each value, first append type and signedness (16#80 signed or
  272. %% 00 unsigned) for all values and then the binary encoded values.
  273. EncodedParams = lists:map(fun encode_param/1, ParamValues),
  274. {TypesAndSigns, EncValues} = lists:unzip(EncodedParams),
  275. iolist_to_binary([Req1, TypesAndSigns, EncValues])
  276. end,
  277. {ok, _SeqNum1} = send_packet(SockModule, Socket, Req, 0),
  278. fetch_execute_response(SockModule, Socket, FilterMap, Timeout).
  279. %% @doc This is used by execute/5. If execute/5 returns {error, timeout}, this
  280. %% function can be called to retry to fetch the results of the query.
  281. fetch_execute_response(SockModule, Socket, Timeout) ->
  282. fetch_execute_response(SockModule, Socket, no_filtermap_fun, Timeout).
  283. fetch_execute_response(SockModule, Socket, FilterMap, Timeout) ->
  284. fetch_response(SockModule, Socket, Timeout, binary, FilterMap, []).
  285. %% @doc Changes the user of the connection.
  286. -spec change_user(module(), term(), iodata(), iodata(), binary(), binary(),
  287. undefined | iodata(), [integer()]) -> #ok{} | #error{}.
  288. change_user(SockModule, Socket, Username, Password, AuthPluginName, AuthPluginData,
  289. Database, ServerVersion) ->
  290. DbBin = case Database of
  291. undefined -> <<>>;
  292. _ -> iolist_to_binary(Database)
  293. end,
  294. Hash = hash_password(AuthPluginName, Password, AuthPluginData),
  295. Req0 = <<?COM_CHANGE_USER, (iolist_to_binary(Username))/binary, 0,
  296. (lenenc_str_encode(Hash))/binary,
  297. DbBin/binary, 0, (character_set(ServerVersion)):16/little>>,
  298. Req1 = case AuthPluginName of
  299. <<>> ->
  300. Req0;
  301. _ ->
  302. <<Req0/binary, AuthPluginName/binary, 0>>
  303. end,
  304. {ok, SeqNum1} = send_packet(SockModule, Socket, Req1, 0),
  305. auth_finish_or_switch(AuthPluginName, AuthPluginData, Password,
  306. SockModule, Socket, ServerVersion, SeqNum1).
  307. -spec reset_connnection(module(), term()) -> #ok{}|#error{}.
  308. reset_connnection(SockModule, Socket) ->
  309. {ok, SeqNum1} = send_packet(SockModule, Socket, <<?COM_RESET_CONNECTION>>, 0),
  310. {ok, Packet, _SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
  311. case Packet of
  312. ?ok_pattern ->
  313. parse_ok_packet(Packet);
  314. ?error_pattern ->
  315. parse_error_packet(Packet)
  316. end.
  317. %% --- internal ---
  318. %% @doc Parses a handshake. This is the first thing that comes from the server
  319. %% when connecting. If an unsupported version or variant of the protocol is used
  320. %% an error is raised.
  321. -spec parse_handshake(binary()) -> #handshake{} | #error{}.
  322. parse_handshake(<<10, Rest/binary>>) ->
  323. %% Protocol version 10.
  324. {ServerVersion, Rest1} = nulterm_str(Rest),
  325. <<ConnectionId:32/little,
  326. AuthPluginDataPart1:8/binary-unit:8,
  327. 0, %% "filler" -- everything below is optional
  328. CapabilitiesLower:16/little,
  329. CharacterSet:8,
  330. StatusFlags:16/little,
  331. CapabilitiesUpper:16/little,
  332. AuthPluginDataLength:8, %% if cabab & CLIENT_PLUGIN_AUTH, otherwise 0
  333. _Reserved:10/binary-unit:8, %% 10 unused (reserved) bytes
  334. Rest3/binary>> = Rest1,
  335. Capabilities = CapabilitiesLower + 16#10000 * CapabilitiesUpper,
  336. Len = case AuthPluginDataLength of
  337. 0 -> 13; %% Server has not CLIENT_PLUGIN_AUTH
  338. K -> K - 8 %% Part 2 length = Total length minus the 8 bytes in part 1.
  339. end,
  340. <<AuthPluginDataPart2:Len/binary-unit:8, AuthPluginName/binary>> = Rest3,
  341. AuthPluginData = <<AuthPluginDataPart1/binary, AuthPluginDataPart2/binary>>,
  342. %% "Due to Bug#59453 the auth-plugin-name is missing the terminating
  343. %% NUL-char in versions prior to 5.5.10 and 5.6.2."
  344. %% Strip the final NUL byte if any.
  345. %% This may also be <<>> in older versions.
  346. L = byte_size(AuthPluginName) - 1,
  347. AuthPluginName1 = case AuthPluginName of
  348. <<AuthPluginNameTrimmed:L/binary, 0>> -> AuthPluginNameTrimmed;
  349. _ -> AuthPluginName
  350. end,
  351. #handshake{server_version = server_version_to_list(ServerVersion),
  352. connection_id = ConnectionId,
  353. capabilities = Capabilities,
  354. character_set = CharacterSet,
  355. status = StatusFlags,
  356. auth_plugin_data = AuthPluginData,
  357. auth_plugin_name = AuthPluginName1};
  358. parse_handshake(<<?ERROR, ErrNo:16/little, Msg/binary>>) ->
  359. %% 'Too many connections' in MariaDB 10.1.21
  360. %% (Error packet in pre-4.1 protocol)
  361. #error{code = ErrNo, msg = Msg};
  362. parse_handshake(<<Protocol:8, _/binary>>) when Protocol /= 10 ->
  363. error(unknown_protocol).
  364. %% @doc Converts a version on the form `<<"5.6.21">' to a list `[5, 6, 21]'.
  365. -spec server_version_to_list(binary()) -> [integer()].
  366. server_version_to_list(ServerVersion) ->
  367. %% This must work with e.g. "5.5.40-0ubuntu0.12.04.1-log" and "5.5.33a".
  368. {match, Parts} = re:run(ServerVersion, <<"^(\\d+)\\.(\\d+)\\.(\\d+)">>,
  369. [{capture, all_but_first, binary}]),
  370. lists:map(fun binary_to_integer/1, Parts).
  371. -spec maybe_do_ssl_upgrade(Host :: inet:socket_address() | inet:hostname(),
  372. SockModule0 :: module(),
  373. Socket0 :: term(),
  374. SeqNum1 :: non_neg_integer(),
  375. Handshake :: #handshake{},
  376. SSLOpts :: undefined | list(),
  377. Database :: iodata() | undefined,
  378. SetFoundRows :: boolean()) ->
  379. {ok, SockModule :: module(), Socket :: term(),
  380. SeqNum2 :: non_neg_integer()}.
  381. maybe_do_ssl_upgrade(_Host, SockModule0, Socket0, SeqNum1, _Handshake,
  382. undefined, _Database, _SetFoundRows) ->
  383. {ok, SockModule0, Socket0, SeqNum1};
  384. maybe_do_ssl_upgrade(Host, gen_tcp, Socket0, SeqNum1, Handshake, SSLOpts,
  385. Database, SetFoundRows) ->
  386. Response = build_handshake_response(Handshake, Database, SetFoundRows),
  387. {ok, SeqNum2} = send_packet(gen_tcp, Socket0, Response, SeqNum1),
  388. case ssl_connect(Host, Socket0, SSLOpts, 5000) of
  389. {ok, SSLSocket} ->
  390. {ok, ssl, SSLSocket, SeqNum2};
  391. {error, Reason} ->
  392. exit({failed_to_upgrade_socket, Reason})
  393. end.
  394. ssl_connect(Host, Port, ConfigSSLOpts, Timeout) ->
  395. DefaultSSLOpts0 = [{versions, [tlsv1]}, {verify, verify_peer}],
  396. DefaultSSLOpts1 = case is_list(Host) andalso inet:parse_address(Host) of
  397. false -> DefaultSSLOpts0;
  398. {ok, _} -> DefaultSSLOpts0;
  399. {error, einval} -> [{server_name_indication, Host} | DefaultSSLOpts0]
  400. end,
  401. MandatorySSLOpts = [{active, false}],
  402. MergedSSLOpts = merge_ssl_options(DefaultSSLOpts1, MandatorySSLOpts, ConfigSSLOpts),
  403. ssl:connect(Port, MergedSSLOpts, Timeout).
  404. -spec merge_ssl_options(list(), list(), list()) -> list().
  405. merge_ssl_options(DefaultSSLOpts, MandatorySSLOpts, ConfigSSLOpts) ->
  406. SSLOpts1 =
  407. lists:foldl(fun({Key, _} = Opt, OptsAcc) ->
  408. lists:keystore(Key, 1, OptsAcc, Opt)
  409. end, DefaultSSLOpts, ConfigSSLOpts),
  410. lists:foldl(fun({Key, _} = Opt, OptsAcc) ->
  411. lists:keystore(Key, 1, OptsAcc, Opt)
  412. end, SSLOpts1, MandatorySSLOpts).
  413. %% @doc This function is used when upgrading to encrypted socket. In other,
  414. %% cases, build_handshake_response/5 is used.
  415. -spec build_handshake_response(#handshake{}, iodata() | undefined, boolean()) ->
  416. binary().
  417. build_handshake_response(Handshake, Database, SetFoundRows) ->
  418. CapabilityFlags = basic_capabilities(Database /= undefined, SetFoundRows),
  419. verify_server_capabilities(Handshake, CapabilityFlags),
  420. ClientCapabilities = add_client_capabilities(CapabilityFlags),
  421. ClientSSLCapabilities = ClientCapabilities bor ?CLIENT_SSL,
  422. CharacterSet = character_set(Handshake#handshake.server_version),
  423. <<ClientSSLCapabilities:32/little,
  424. ?MAX_BYTES_PER_PACKET:32/little,
  425. CharacterSet:8,
  426. 0:23/unit:8>>.
  427. %% @doc The response sent by the client to the server after receiving the
  428. %% initial handshake from the server
  429. -spec build_handshake_response(#handshake{}, iodata(), iodata(),
  430. iodata() | undefined, boolean()) -> binary().
  431. build_handshake_response(Handshake, Username, Password, Database,
  432. SetFoundRows) ->
  433. CapabilityFlags = basic_capabilities(Database /= undefined, SetFoundRows),
  434. verify_server_capabilities(Handshake, CapabilityFlags),
  435. %% Add some extra capability flags only for signalling to the server what
  436. %% the client wants to do. The server doesn't say it handles them although
  437. %% it does. (http://bugs.mysql.com/bug.php?id=42268)
  438. ClientCapabilityFlags = add_client_capabilities(CapabilityFlags),
  439. Hash = hash_password(Handshake#handshake.auth_plugin_name, Password,
  440. Handshake#handshake.auth_plugin_data),
  441. HashLength = size(Hash),
  442. CharacterSet = character_set(Handshake#handshake.server_version),
  443. UsernameUtf8 = unicode:characters_to_binary(Username),
  444. DbBin = case Database of
  445. undefined -> <<>>;
  446. _ -> <<(iolist_to_binary(Database))/binary, 0>>
  447. end,
  448. <<ClientCapabilityFlags:32/little,
  449. ?MAX_BYTES_PER_PACKET:32/little,
  450. CharacterSet:8,
  451. 0:23/unit:8, %% reserverd
  452. UsernameUtf8/binary,
  453. 0, %% NUL-terminator for the username
  454. HashLength,
  455. Hash/binary,
  456. DbBin/binary>>.
  457. -spec verify_server_capabilities(Handshake :: #handshake{},
  458. CapabilityFlags :: integer()) ->
  459. true | no_return().
  460. verify_server_capabilities(Handshake, CapabilityFlags) ->
  461. %% We require these capabilities. Make sure the server handles them.
  462. Handshake#handshake.capabilities band CapabilityFlags == CapabilityFlags
  463. orelse error(old_server_version).
  464. -spec basic_capabilities(ConnectWithDB :: boolean(),
  465. SetFoundRows :: boolean()) -> integer().
  466. basic_capabilities(ConnectWithDB, SetFoundRows) ->
  467. CapabilityFlags0 = ?CLIENT_PROTOCOL_41 bor
  468. ?CLIENT_TRANSACTIONS bor
  469. ?CLIENT_SECURE_CONNECTION,
  470. CapabilityFlags1 = case ConnectWithDB of
  471. true -> CapabilityFlags0 bor ?CLIENT_CONNECT_WITH_DB;
  472. _ -> CapabilityFlags0
  473. end,
  474. case SetFoundRows of
  475. true -> CapabilityFlags1 bor ?CLIENT_FOUND_ROWS;
  476. _ -> CapabilityFlags1
  477. end.
  478. -spec add_client_capabilities(Caps :: integer()) -> integer().
  479. add_client_capabilities(Caps) ->
  480. Caps bor
  481. ?CLIENT_MULTI_STATEMENTS bor
  482. ?CLIENT_MULTI_RESULTS bor
  483. ?CLIENT_PS_MULTI_RESULTS bor
  484. ?CLIENT_PLUGIN_AUTH bor
  485. ?CLIENT_LONG_PASSWORD.
  486. -spec character_set([integer()]) -> integer().
  487. character_set(ServerVersion) when ServerVersion >= [5, 5, 3] ->
  488. %% https://dev.mysql.com/doc/relnotes/mysql/5.5/en/news-5-5-3.html
  489. ?UTF8MB4;
  490. character_set(_ServerVersion) ->
  491. ?UTF8MB3.
  492. %% @doc Handles the second packet from the server, when we have replied to the
  493. %% initial handshake. Returns an error if the server returns an error. Raises
  494. %% an error if unimplemented features are required.
  495. -spec parse_handshake_confirm(binary()) ->
  496. #ok{} | #auth_method_switch{} | #error{} | auth_more_data().
  497. parse_handshake_confirm(Packet = ?ok_pattern) ->
  498. %% Connection complete.
  499. parse_ok_packet(Packet);
  500. parse_handshake_confirm(Packet = ?error_pattern) ->
  501. %% Access denied, insufficient client capabilities, etc.
  502. parse_error_packet(Packet);
  503. parse_handshake_confirm(<<?EOF>>) ->
  504. %% "Old Authentication Method Switch Request Packet consisting of a
  505. %% single 0xfe byte. It is sent by server to request client to
  506. %% switch to Old Password Authentication if CLIENT_PLUGIN_AUTH
  507. %% capability is not supported (by either the client or the server)"
  508. error(old_auth);
  509. parse_handshake_confirm(<<?EOF, AuthMethodSwitch/binary>>) ->
  510. %% "Authentication Method Switch Request Packet. If both server and
  511. %% client support CLIENT_PLUGIN_AUTH capability, server can send
  512. %% this packet to ask client to use another authentication method."
  513. parse_auth_method_switch(AuthMethodSwitch);
  514. parse_handshake_confirm(<<?MORE_DATA, MoreData/binary>>) ->
  515. %% More Data Packet consisting of a 0x01 byte and a payload. This
  516. %% kind of packet may be used in the authentication process to
  517. %% provide more data to the client. It is usually followed by
  518. %% either an OK Packet, an Error Packet, or another More Data
  519. %% packet.
  520. parse_auth_more_data(MoreData).
  521. %% -- both text and binary protocol --
  522. %% @doc Fetches one or more results and and parses the result set(s) using
  523. %% either the text format (for plain queries) or the binary format (for
  524. %% prepared statements).
  525. -spec fetch_response(module(), term(), timeout(), text | binary,
  526. query_filtermap(), list()) ->
  527. {ok, [#ok{} | #resultset{} | #error{}]} | {error, timeout}.
  528. fetch_response(SockModule, Socket, Timeout, Proto, FilterMap, Acc) ->
  529. case recv_packet(SockModule, Socket, Timeout, any) of
  530. {ok, Packet, SeqNum2} ->
  531. Result = case Packet of
  532. ?ok_pattern ->
  533. parse_ok_packet(Packet);
  534. ?error_pattern ->
  535. parse_error_packet(Packet);
  536. ResultPacket ->
  537. %% The first packet in a resultset is only the column count.
  538. {ColCount, <<>>} = lenenc_int(ResultPacket),
  539. fetch_resultset(SockModule, Socket, ColCount, Proto,
  540. FilterMap, SeqNum2)
  541. end,
  542. Acc1 = [Result | Acc],
  543. case more_results_exists(Result) of
  544. true ->
  545. fetch_response(SockModule, Socket, Timeout, Proto,
  546. FilterMap, Acc1);
  547. false ->
  548. {ok, lists:reverse(Acc1)}
  549. end;
  550. {error, timeout} ->
  551. {error, timeout}
  552. end.
  553. %% @doc Fetches a result set.
  554. -spec fetch_resultset(module(), term(), integer(), text | binary,
  555. query_filtermap(), integer()) ->
  556. #resultset{} | #error{}.
  557. fetch_resultset(SockModule, Socket, FieldCount, Proto, FilterMap, SeqNum0) ->
  558. {ok, ColDefs0, SeqNum1} = fetch_column_definitions(SockModule, Socket,
  559. SeqNum0, FieldCount, []),
  560. {ok, DelimPacket, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
  561. #eof{} = parse_eof_packet(DelimPacket),
  562. ColDefs1 = lists:map(fun parse_column_definition/1, ColDefs0),
  563. case fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs1, Proto,
  564. FilterMap, SeqNum2, []) of
  565. {ok, Rows, _SeqNum3, #eof{status = S, warning_count = W}} ->
  566. #resultset{cols = ColDefs1, rows = Rows, status = S,
  567. warning_count = W};
  568. #error{} = E ->
  569. E
  570. end.
  571. %% @doc Fetches the rows for a result set and decodes them using either the text
  572. %% format (for plain queries) or binary format (for prepared statements).
  573. -spec fetch_resultset_rows(module(), term(), integer(), [#col{}], text | binary,
  574. query_filtermap(), integer(), [[term()]]) ->
  575. {ok, [[term()]], integer(), #eof{}} | #error{}.
  576. fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs, Proto,
  577. FilterMap, SeqNum0, Acc) ->
  578. {ok, Packet, SeqNum1} = recv_packet(SockModule, Socket, SeqNum0),
  579. case Packet of
  580. ?error_pattern ->
  581. parse_error_packet(Packet);
  582. ?eof_pattern ->
  583. Eof = parse_eof_packet(Packet),
  584. {ok, lists:reverse(Acc), SeqNum1, Eof};
  585. RowPacket ->
  586. Row0=decode_row(FieldCount, ColDefs, RowPacket, Proto),
  587. Acc1 = case filtermap_resultset_row(FilterMap, ColDefs, Row0) of
  588. false ->
  589. Acc;
  590. true ->
  591. [Row0|Acc];
  592. {true, Row1} ->
  593. [Row1|Acc]
  594. end,
  595. fetch_resultset_rows(SockModule, Socket, FieldCount, ColDefs,
  596. Proto, FilterMap, SeqNum1, Acc1)
  597. end.
  598. -spec filtermap_resultset_row(query_filtermap(), [#col{}], [term()]) ->
  599. query_filtermap_res().
  600. filtermap_resultset_row(no_filtermap_fun, _, _) ->
  601. true;
  602. filtermap_resultset_row(Fun, _, Row) when is_function(Fun, 1) ->
  603. Fun(Row);
  604. filtermap_resultset_row(Fun, ColDefs, Row) when is_function(Fun, 2) ->
  605. Fun([Col#col.name || Col <- ColDefs], Row).
  606. more_results_exists(#ok{status = S}) ->
  607. S band ?SERVER_MORE_RESULTS_EXISTS /= 0;
  608. more_results_exists(#error{}) ->
  609. false; %% No status bits for error
  610. more_results_exists(#resultset{status = S}) ->
  611. S band ?SERVER_MORE_RESULTS_EXISTS /= 0.
  612. %% @doc Receives NumLeft column definition packets. They are not parsed.
  613. %% @see parse_column_definition/1
  614. -spec fetch_column_definitions(module(), term(), SeqNum :: integer(),
  615. NumLeft :: integer(), Acc :: [binary()]) ->
  616. {ok, ColDefPackets :: [binary()], NextSeqNum :: integer()}.
  617. fetch_column_definitions(SockModule, Socket, SeqNum, NumLeft, Acc)
  618. when NumLeft > 0 ->
  619. {ok, Packet, SeqNum1} = recv_packet(SockModule, Socket, SeqNum),
  620. fetch_column_definitions(SockModule, Socket, SeqNum1, NumLeft - 1,
  621. [Packet | Acc]);
  622. fetch_column_definitions(_SockModule, _Socket, SeqNum, 0, Acc) ->
  623. {ok, lists:reverse(Acc), SeqNum}.
  624. %% Parses a packet containing a column definition (part of a result set)
  625. parse_column_definition(Data) ->
  626. {<<"def">>, Rest1} = lenenc_str(Data), %% catalog (always "def")
  627. {_Schema, Rest2} = lenenc_str(Rest1), %% schema-name
  628. {_Table, Rest3} = lenenc_str(Rest2), %% virtual table-name
  629. {_OrgTable, Rest4} = lenenc_str(Rest3), %% physical table-name
  630. {Name, Rest5} = lenenc_str(Rest4), %% virtual column name
  631. {_OrgName, Rest6} = lenenc_str(Rest5), %% physical column name
  632. {16#0c, Rest7} = lenenc_int(Rest6), %% length of the following fields
  633. %% (always 0x0c)
  634. <<Charset:16/little, %% column character set
  635. Length:32/little, %% maximum length of the field
  636. Type:8, %% type of the column as defined in Column Type
  637. Flags:16/little, %% flags
  638. Decimals:8, %% max shown decimal digits:
  639. 0, %% "filler" %% - 0x00 for integers and static strings
  640. 0, %% - 0x1f for dynamic strings, double, float
  641. Rest8/binary>> = Rest7, %% - 0x00 to 0x51 for decimals
  642. %% Here, if command was COM_FIELD_LIST {
  643. %% default values: lenenc_str
  644. %% }
  645. <<>> = Rest8,
  646. #col{name = Name, type = Type, charset = Charset, length = Length,
  647. decimals = Decimals, flags = Flags}.
  648. %% @doc Decodes a row using either the text or binary format.
  649. -spec decode_row(integer(), [#col{}], binary(), text | binary) -> [term()].
  650. decode_row(FieldCount, ColDefs, RowPacket, text) ->
  651. decode_text_row(FieldCount, ColDefs, RowPacket);
  652. decode_row(FieldCount, ColDefs, RowPacket, binary) ->
  653. decode_binary_row(FieldCount, ColDefs, RowPacket).
  654. %% -- text protocol --
  655. -spec decode_text_row(NumColumns :: integer(),
  656. ColumnDefinitions :: [#col{}],
  657. Data :: binary()) -> [term()].
  658. decode_text_row(_NumColumns, ColumnDefs, Data) ->
  659. decode_text_row_acc(ColumnDefs, Data, []).
  660. %% parses Data using ColDefs and builds the values Acc.
  661. decode_text_row_acc([ColDef | ColDefs], Data, Acc) ->
  662. case Data of
  663. <<16#fb, Rest/binary>> ->
  664. %% NULL
  665. decode_text_row_acc(ColDefs, Rest, [null | Acc]);
  666. _ ->
  667. %% Every thing except NULL
  668. {Text, Rest} = lenenc_str(Data),
  669. Term = decode_text(ColDef, Text),
  670. decode_text_row_acc(ColDefs, Rest, [Term | Acc])
  671. end;
  672. decode_text_row_acc([], <<>>, Acc) ->
  673. lists:reverse(Acc).
  674. %% @doc When receiving data in the text protocol, we get everything as binaries
  675. %% (except NULL). This function is used to parse these string values.
  676. decode_text(#col{type = T}, Text)
  677. when T == ?TYPE_TINY; T == ?TYPE_SHORT; T == ?TYPE_LONG; T == ?TYPE_LONGLONG;
  678. T == ?TYPE_INT24; T == ?TYPE_YEAR ->
  679. binary_to_integer(Text);
  680. decode_text(#col{type = T}, Text)
  681. when T == ?TYPE_STRING; T == ?TYPE_VARCHAR; T == ?TYPE_VAR_STRING;
  682. T == ?TYPE_ENUM; T == ?TYPE_SET; T == ?TYPE_LONG_BLOB;
  683. T == ?TYPE_MEDIUM_BLOB; T == ?TYPE_BLOB; T == ?TYPE_TINY_BLOB;
  684. T == ?TYPE_GEOMETRY; T == ?TYPE_JSON ->
  685. %% As of MySQL 5.6.21 we receive SET and ENUM values as STRING, i.e. we
  686. %% cannot convert them to atom() or sets:set(), etc.
  687. Text;
  688. decode_text(#col{type = ?TYPE_BIT, length = Length}, Text) ->
  689. %% Convert to <<_:Length/bitstring>>
  690. decode_bitstring(Text, Length);
  691. decode_text(#col{type = T, decimals = S, length = L}, Text)
  692. when T == ?TYPE_DECIMAL; T == ?TYPE_NEWDECIMAL ->
  693. %% Length is the max number of symbols incl. dot and minus sign, e.g. the
  694. %% number of digits plus 2.
  695. decode_decimal(Text, L - 2, S);
  696. decode_text(#col{type = ?TYPE_DATE},
  697. <<Y:4/binary, "-", M:2/binary, "-", D:2/binary>>) ->
  698. {binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)};
  699. decode_text(#col{type = ?TYPE_TIME}, Text) ->
  700. {match, [Sign, Hbin, Mbin, Sbin, Frac]} =
  701. re:run(Text,
  702. <<"^(-?)(\\d+):(\\d+):(\\d+)(\\.?\\d*)$">>,
  703. [{capture, all_but_first, binary}]),
  704. H = binary_to_integer(Hbin),
  705. M = binary_to_integer(Mbin),
  706. S = binary_to_integer(Sbin),
  707. IsNeg = Sign == <<"-">>,
  708. Fraction = case Frac of
  709. <<>> -> 0;
  710. _ when not IsNeg -> binary_to_float(<<"0", Frac/binary>>);
  711. _ when IsNeg -> 1 - binary_to_float(<<"0", Frac/binary>>)
  712. end,
  713. Sec1 = H * 3600 + M * 60 + S,
  714. Sec2 = if IsNeg -> -Sec1; true -> Sec1 end,
  715. Sec3 = if IsNeg and (Fraction /= 0) -> Sec2 - 1;
  716. true -> Sec2
  717. end,
  718. {Days, {Hours, Minutes, Seconds}} = calendar:seconds_to_daystime(Sec3),
  719. {Days, {Hours, Minutes, Seconds + Fraction}};
  720. decode_text(#col{type = T},
  721. <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
  722. H:2/binary, ":", Mi:2/binary, ":", S:2/binary>>)
  723. when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
  724. %% Without fractions.
  725. {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
  726. {binary_to_integer(H), binary_to_integer(Mi), binary_to_integer(S)}};
  727. decode_text(#col{type = T},
  728. <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
  729. H:2/binary, ":", Mi:2/binary, ":", FloatS/binary>>)
  730. when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
  731. %% With fractions.
  732. {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
  733. {binary_to_integer(H), binary_to_integer(Mi), binary_to_float(FloatS)}};
  734. decode_text(#col{type = T}, Text) when T == ?TYPE_FLOAT;
  735. T == ?TYPE_DOUBLE ->
  736. try binary_to_float(Text)
  737. catch error:badarg ->
  738. try binary_to_integer(Text) of
  739. Int -> float(Int)
  740. catch error:badarg ->
  741. %% It is something like "4e75" that must be turned into "4.0e75"
  742. binary_to_float(binary:replace(Text, <<"e">>, <<".0e">>))
  743. end
  744. end.
  745. %% -- binary protocol --
  746. %% @doc If NumColumns is non-zero, fetches this number of column definitions
  747. %% and an EOF packet. Used by prepare/3.
  748. fetch_column_definitions_if_any(0, _SockModule, _Socket, SeqNum) ->
  749. {[], SeqNum};
  750. fetch_column_definitions_if_any(N, SockModule, Socket, SeqNum) ->
  751. {ok, Defs, SeqNum1} = fetch_column_definitions(SockModule, Socket, SeqNum,
  752. N, []),
  753. {ok, ?eof_pattern, SeqNum2} = recv_packet(SockModule, Socket, SeqNum1),
  754. {Defs, SeqNum2}.
  755. %% @doc Decodes a packet representing a row in a binary result set.
  756. %% It consists of a 0 byte, then a null bitmap, then the values.
  757. %% Returns a list of length NumColumns with terms of appropriate types for each
  758. %% MySQL type in ColumnTypes.
  759. -spec decode_binary_row(NumColumns :: integer(),
  760. ColumnDefs :: [#col{}],
  761. Data :: binary()) -> [term()].
  762. decode_binary_row(NumColumns, ColumnDefs, <<0, Data/binary>>) ->
  763. {NullBitMap, Rest} = null_bitmap_decode(NumColumns, Data, 2),
  764. decode_binary_row_acc(ColumnDefs, NullBitMap, Rest, []).
  765. %% @doc Accumulating helper for decode_binary_row/3.
  766. decode_binary_row_acc([_|ColDefs], <<1:1, NullBitMap/bitstring>>, Data, Acc) ->
  767. %% NULL
  768. decode_binary_row_acc(ColDefs, NullBitMap, Data, [null | Acc]);
  769. decode_binary_row_acc([ColDef | ColDefs], <<0:1, NullBitMap/bitstring>>, Data,
  770. Acc) ->
  771. %% Not NULL
  772. {Term, Rest} = decode_binary(ColDef, Data),
  773. decode_binary_row_acc(ColDefs, NullBitMap, Rest, [Term | Acc]);
  774. decode_binary_row_acc([], _, <<>>, Acc) ->
  775. lists:reverse(Acc).
  776. %% @doc Decodes a null bitmap as stored by MySQL and returns it in a strait
  777. %% bitstring counting bits from left to right in a tuple with remaining data.
  778. %%
  779. %% In the MySQL null bitmap the bits are stored counting bytes from the left and
  780. %% bits within each byte from the right. (Sort of little endian.)
  781. -spec null_bitmap_decode(NumColumns :: integer(), Data :: binary(),
  782. BitOffset :: integer()) ->
  783. {NullBitstring :: bitstring(), Rest :: binary()}.
  784. null_bitmap_decode(NumColumns, Data, BitOffset) ->
  785. %% Binary shift right by 3 is equivallent to integer division by 8.
  786. BitMapLength = (NumColumns + BitOffset + 7) bsr 3,
  787. <<NullBitstring0:BitMapLength/binary, Rest/binary>> = Data,
  788. <<_:BitOffset, NullBitstring:NumColumns/bitstring, _/bitstring>> =
  789. << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= NullBitstring0 >>,
  790. {NullBitstring, Rest}.
  791. %% @doc The reverse of null_bitmap_decode/3. The number of columns is taken to
  792. %% be the number of bits in NullBitstring. Returns the MySQL null bitmap as a
  793. %% binary (i.e. full bytes). BitOffset is the number of unused bits that should
  794. %% be inserted before the other bits.
  795. -spec null_bitmap_encode(bitstring(), integer()) -> binary().
  796. null_bitmap_encode(NullBitstring, BitOffset) ->
  797. PayloadLength = bit_size(NullBitstring) + BitOffset,
  798. %% Round up to a multiple of 8.
  799. BitMapLength = (PayloadLength + 7) band bnot 7,
  800. PadBitsLength = BitMapLength - PayloadLength,
  801. PaddedBitstring = <<0:BitOffset, NullBitstring/bitstring, 0:PadBitsLength>>,
  802. << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= PaddedBitstring >>.
  803. %% Reverses the bits in a byte.
  804. reverse_byte(<<A:1, B:1, C:1, D:1, E:1, F:1, G:1, H:1>>) ->
  805. <<H:1, G:1, F:1, E:1, D:1, C:1, B:1, A:1>>.
  806. %% @doc Used for executing prepared statements. The bit offset whould be 0 in
  807. %% this case.
  808. -spec build_null_bitmap([any()]) -> binary().
  809. build_null_bitmap(Values) ->
  810. Bits = << <<(case V of null -> 1; _ -> 0 end):1>> || V <- Values >>,
  811. null_bitmap_encode(Bits, 0).
  812. %% Decodes a value as received in the 'binary protocol' result set.
  813. %%
  814. %% The types are type constants for the binary protocol, such as
  815. %% ProtocolBinary::MYSQL_TYPE_STRING. In the guide "MySQL Internals" these are
  816. %% not listed, but we assume that are the same as for the text protocol.
  817. -spec decode_binary(ColDef :: #col{}, Data :: binary()) ->
  818. {Term :: term(), Rest :: binary()}.
  819. decode_binary(#col{type = T}, Data)
  820. when T == ?TYPE_STRING; T == ?TYPE_VARCHAR; T == ?TYPE_VAR_STRING;
  821. T == ?TYPE_ENUM; T == ?TYPE_SET; T == ?TYPE_LONG_BLOB;
  822. T == ?TYPE_MEDIUM_BLOB; T == ?TYPE_BLOB; T == ?TYPE_TINY_BLOB;
  823. T == ?TYPE_GEOMETRY; T == ?TYPE_JSON ->
  824. %% As of MySQL 5.6.21 we receive SET and ENUM values as STRING, i.e. we
  825. %% cannot convert them to atom() or sets:set(), etc.
  826. lenenc_str(Data);
  827. decode_binary(#col{type = ?TYPE_LONGLONG, flags = F},
  828. <<Value:64/signed-little, Rest/binary>>)
  829. when F band ?UNSIGNED_FLAG == 0 ->
  830. {Value, Rest};
  831. decode_binary(#col{type = ?TYPE_LONGLONG, flags = F},
  832. <<Value:64/unsigned-little, Rest/binary>>)
  833. when F band ?UNSIGNED_FLAG /= 0 ->
  834. {Value, Rest};
  835. decode_binary(#col{type = T, flags = F},
  836. <<Value:32/signed-little, Rest/binary>>)
  837. when (T == ?TYPE_LONG orelse T == ?TYPE_INT24) andalso
  838. F band ?UNSIGNED_FLAG == 0 ->
  839. {Value, Rest};
  840. decode_binary(#col{type = T, flags = F},
  841. <<Value:32/unsigned-little, Rest/binary>>)
  842. when (T == ?TYPE_LONG orelse T == ?TYPE_INT24) andalso
  843. F band ?UNSIGNED_FLAG /= 0 ->
  844. {Value, Rest};
  845. decode_binary(#col{type = ?TYPE_SHORT, flags = F},
  846. <<Value:16/signed-little, Rest/binary>>)
  847. when F band ?UNSIGNED_FLAG == 0 ->
  848. {Value, Rest};
  849. decode_binary(#col{type = T, flags = F},
  850. <<Value:16/unsigned-little, Rest/binary>>)
  851. when (T == ?TYPE_SHORT orelse T == ?TYPE_YEAR) andalso
  852. F band ?UNSIGNED_FLAG /= 0 ->
  853. {Value, Rest};
  854. decode_binary(#col{type = ?TYPE_TINY, flags = F},
  855. <<Value:8/unsigned, Rest/binary>>)
  856. when F band ?UNSIGNED_FLAG /= 0 ->
  857. {Value, Rest};
  858. decode_binary(#col{type = ?TYPE_TINY, flags = F},
  859. <<Value:8/signed, Rest/binary>>)
  860. when F band ?UNSIGNED_FLAG == 0 ->
  861. {Value, Rest};
  862. decode_binary(#col{type = T, decimals = S, length = L}, Data)
  863. when T == ?TYPE_DECIMAL; T == ?TYPE_NEWDECIMAL ->
  864. %% Length is the max number of symbols incl. dot and minus sign, e.g. the
  865. %% number of digits plus 2.
  866. {Binary, Rest} = lenenc_str(Data),
  867. {decode_decimal(Binary, L - 2, S), Rest};
  868. decode_binary(#col{type = ?TYPE_DOUBLE},
  869. <<Value:64/float-little, Rest/binary>>) ->
  870. {Value, Rest};
  871. decode_binary(#col{type = ?TYPE_FLOAT}, <<0.0:32/float-little, Rest/binary>>) ->
  872. %% TYPE_FLOAT conversation fails on math:log10(0.0)
  873. {0.0, Rest};
  874. decode_binary(#col{type = ?TYPE_FLOAT},
  875. <<Value:32/float-little, Rest/binary>>) ->
  876. %% There is a precision loss when storing and fetching a 32-bit float.
  877. %% In the text protocol, it is obviously rounded. Storing 3.14 in a FLOAT
  878. %% column and fetching it using the text protocol, we get "3.14" which we
  879. %% parse to the Erlang double as close as possible to 3.14. Fetching the
  880. %% same value as a binary 32-bit float, we get 3.140000104904175. To achieve
  881. %% the same rounding after receiving it as a 32-bit float, we try to do the
  882. %% same rounding here as MySQL does when sending it over the text protocol.
  883. %%
  884. %% This comment explains the idea:
  885. %%
  886. %% Posted by Geoffrey Downs on March 10 2011 10:26am
  887. %%
  888. %% Following up... I *think* this is correct for the default float
  889. %% columns in mysql:
  890. %%
  891. %% var yourNumber = some floating point value
  892. %% max decimal precision = 10 ^ (-5 + flooring(yourNumber log 10))
  893. %% So:
  894. %% 0 < x < 10 -> max precision is 0.00001
  895. %% 10 <= x < 100 -> max precision is 0.0001
  896. %% 100 <= x < 1000 -> max precision is 0.001
  897. %% etc.
  898. %%
  899. %% (From http://dev.mysql.com/doc/refman/5.7/en/problems-with-float.html
  900. %% fetched 10 Nov 2014)
  901. %%
  902. %% The above is almost correct, except for the example in the interval
  903. %% 0 < x < 1. There are 6 significant digits also for these numbers.
  904. %%
  905. %% Now, instead of P = 0.00001 we want the inverse 100000.0 but if we
  906. %% compute Factor = 1 / P we get a precision loss, so instead we do this:
  907. Factor = math:pow(10, flooring(6 - math:log10(abs(Value)))),
  908. RoundedValue = round(Value * Factor) / Factor,
  909. {RoundedValue, Rest};
  910. decode_binary(#col{type = ?TYPE_BIT, length = Length}, Data) ->
  911. {Binary, Rest} = lenenc_str(Data),
  912. %% Convert to <<_:Length/bitstring>>
  913. {decode_bitstring(Binary, Length), Rest};
  914. decode_binary(#col{type = ?TYPE_DATE}, Data) ->
  915. %% Coded in the same way as DATETIME and TIMESTAMP below, but returned in
  916. %% a simple triple.
  917. case lenenc_int(Data) of
  918. {0, Rest} -> {{0, 0, 0}, Rest};
  919. {4, <<Y:16/little, M, D, Rest/binary>>} -> {{Y, M, D}, Rest}
  920. end;
  921. decode_binary(#col{type = T}, Data)
  922. when T == ?TYPE_DATETIME; T == ?TYPE_TIMESTAMP ->
  923. %% length (1) -- number of bytes following (valid values: 0, 4, 7, 11)
  924. case lenenc_int(Data) of
  925. {0, Rest} ->
  926. {{{0, 0, 0}, {0, 0, 0}}, Rest};
  927. {4, <<Y:16/little, M, D, Rest/binary>>} ->
  928. {{{Y, M, D}, {0, 0, 0}}, Rest};
  929. {7, <<Y:16/little, M, D, H, Mi, S, Rest/binary>>} ->
  930. {{{Y, M, D}, {H, Mi, S}}, Rest};
  931. {11, <<Y:16/little, M, D, H, Mi, S, Micro:32/little, Rest/binary>>} ->
  932. {{{Y, M, D}, {H, Mi, S + 0.000001 * Micro}}, Rest}
  933. end;
  934. decode_binary(#col{type = ?TYPE_TIME}, Data) ->
  935. %% length (1) -- number of bytes following (valid values: 0, 8, 12)
  936. %% is_negative (1) -- (1 if minus, 0 for plus)
  937. %% days (4) -- days
  938. %% hours (1) -- hours
  939. %% minutes (1) -- minutes
  940. %% seconds (1) -- seconds
  941. %% micro_seconds (4) -- micro-seconds
  942. case lenenc_int(Data) of
  943. {0, Rest} ->
  944. {{0, {0, 0, 0}}, Rest};
  945. {8, <<0, D:32/little, H, M, S, Rest/binary>>} ->
  946. {{D, {H, M, S}}, Rest};
  947. {12, <<0, D:32/little, H, M, S, Micro:32/little, Rest/binary>>} ->
  948. {{D, {H, M, S + 0.000001 * Micro}}, Rest};
  949. {8, <<1, D:32/little, H, M, S, Rest/binary>>} ->
  950. %% Negative time. Example: '-00:00:01' --> {-1,{23,59,59}}
  951. Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
  952. %Seconds = D * 86400 + calendar:time_to_seconds({H, M, S}),
  953. {calendar:seconds_to_daystime(-Seconds), Rest};
  954. {12, <<1, D:32/little, H, M, S, Micro:32/little, Rest/binary>>}
  955. when Micro > 0 ->
  956. %% Negate and convert to seconds, excl fractions
  957. Seconds = -(((D * 24 + H) * 60 + M) * 60 + S),
  958. %Seconds = -D * 86400 - calendar:time_to_seconds({H, M, S}),
  959. %% Subtract 1 second for the fractions
  960. {Days, {Hours, Minutes, Sec}} =
  961. calendar:seconds_to_daystime(Seconds - 1),
  962. %% Adding the fractions to Sec again makes it a float
  963. {{Days, {Hours, Minutes, Sec + 1 - 0.000001 * Micro}}, Rest}
  964. end.
  965. %% @doc Like trunc/1 but towards negative infinity instead of towards zero.
  966. flooring(Value) ->
  967. Trunc = trunc(Value),
  968. if
  969. Trunc =< Value -> Trunc;
  970. Trunc > Value -> Trunc - 1 %% for negative values
  971. end.
  972. %% @doc Encodes a term reprenting av value as a binary for use in the binary
  973. %% protocol. As this is used to encode parameters for prepared statements, the
  974. %% encoding is in its required form, namely `<<Type:8, Sign:8, Value/binary>>'.
  975. -spec encode_param(term()) -> {TypeAndSign :: binary(), Data :: binary()}.
  976. encode_param(null) ->
  977. {<<?TYPE_NULL, 0>>, <<>>};
  978. encode_param(Value) when is_binary(Value) ->
  979. EncLength = lenenc_int_encode(byte_size(Value)),
  980. {<<?TYPE_VAR_STRING, 0>>, <<EncLength/binary, Value/binary>>};
  981. encode_param(Value) when is_list(Value) ->
  982. encode_param(unicode:characters_to_binary(Value));
  983. encode_param(Value) when is_integer(Value), Value >= 0 ->
  984. %% We send positive integers with the 'unsigned' flag set.
  985. if
  986. Value =< 16#ff ->
  987. {<<?TYPE_TINY, 16#80>>, <<Value:8>>};
  988. Value =< 16#ffff ->
  989. {<<?TYPE_SHORT, 16#80>>, <<Value:16/little>>};
  990. Value =< 16#ffffffff ->
  991. {<<?TYPE_LONG, 16#80>>, <<Value:32/little>>};
  992. Value =< 16#ffffffffffffffff ->
  993. {<<?TYPE_LONGLONG, 16#80>>, <<Value:64/little>>};
  994. true ->
  995. %% If larger than a 64-bit int we send it as a string. MySQL does
  996. %% silently cast strings in aithmetic expressions. Also, DECIMALs
  997. %% are always sent as strings.
  998. encode_param(integer_to_binary(Value))
  999. end;
  1000. encode_param(Value) when is_integer(Value), Value < 0 ->
  1001. if
  1002. Value >= -16#80 ->
  1003. {<<?TYPE_TINY, 0>>, <<Value:8>>};
  1004. Value >= -16#8000 ->
  1005. {<<?TYPE_SHORT, 0>>, <<Value:16/little>>};
  1006. Value >= -16#80000000 ->
  1007. {<<?TYPE_LONG, 0>>, <<Value:32/little>>};
  1008. Value >= -16#8000000000000000 ->
  1009. {<<?TYPE_LONGLONG, 0>>, <<Value:64/little>>};
  1010. true ->
  1011. encode_param(integer_to_binary(Value))
  1012. end;
  1013. encode_param(Value) when is_float(Value) ->
  1014. {<<?TYPE_DOUBLE, 0>>, <<Value:64/float-little>>};
  1015. encode_param(Value) when is_bitstring(Value) ->
  1016. Binary = encode_bitstring(Value),
  1017. EncLength = lenenc_int_encode(byte_size(Binary)),
  1018. {<<?TYPE_VAR_STRING, 0>>, <<EncLength/binary, Binary/binary>>};
  1019. encode_param({Y, M, D}) ->
  1020. %% calendar:date()
  1021. {<<?TYPE_DATE, 0>>, <<4, Y:16/little, M, D>>};
  1022. encode_param({{Y, M, D}, {0, 0, 0}}) ->
  1023. %% Datetime at midnight
  1024. {<<?TYPE_DATETIME, 0>>, <<4, Y:16/little, M, D>>};
  1025. encode_param({{Y, M, D}, {H, Mi, S}}) when is_integer(S) ->
  1026. %% calendar:datetime()
  1027. {<<?TYPE_DATETIME, 0>>, <<7, Y:16/little, M, D, H, Mi, S>>};
  1028. encode_param({{Y, M, D}, {H, Mi, S}}) when is_float(S) ->
  1029. %% calendar:datetime() with a float for seconds. This way it looks very
  1030. %% similar to a datetime. Microseconds in MySQL timestamps are possible but
  1031. %% not very common.
  1032. Sec = trunc(S),
  1033. Micro = round(1000000 * (S - Sec)),
  1034. {<<?TYPE_DATETIME, 0>>, <<11, Y:16/little, M, D, H, Mi, Sec,
  1035. Micro:32/little>>};
  1036. encode_param({D, {H, M, S}}) when is_integer(S), D >= 0 ->
  1037. %% calendar:seconds_to_daystime()
  1038. {<<?TYPE_TIME, 0>>, <<8, 0, D:32/little, H, M, S>>};
  1039. encode_param({D, {H, M, S}}) when is_integer(S), D < 0 ->
  1040. %% Convert to seconds, negate and convert back to daystime form.
  1041. %% Then set the minus flag.
  1042. Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
  1043. {D1, {H1, M1, S1}} = calendar:seconds_to_daystime(-Seconds),
  1044. {<<?TYPE_TIME, 0>>, <<8, 1, D1:32/little, H1, M1, S1>>};
  1045. encode_param({D, {H, M, S}}) when is_float(S), D >= 0 ->
  1046. S1 = trunc(S),
  1047. Micro = round(1000000 * (S - S1)),
  1048. {<<?TYPE_TIME, 0>>, <<12, 0, D:32/little, H, M, S1, Micro:32/little>>};
  1049. encode_param({D, {H, M, S}}) when is_float(S), S > 0.0, D < 0 ->
  1050. IntS = trunc(S),
  1051. Micro = round(1000000 * (1 - S + IntS)),
  1052. Seconds = (D * 24 + H) * 3600 + M * 60 + IntS + 1,
  1053. {D1, {M1, H1, S1}} = calendar:seconds_to_daystime(-Seconds),
  1054. {<<?TYPE_TIME, 0>>, <<12, 1, D1:32/little, H1, M1, S1, Micro:32/little>>};
  1055. encode_param({D, {H, M, 0.0}}) ->
  1056. encode_param({D, {H, M, 0}}).
  1057. %% @doc Checks if the given Parameters can be encoded for use in the
  1058. %% binary protocol. Returns `true' if all of the parameters can be
  1059. %% encoded, `false' if any of them cannot be encoded.
  1060. -spec valid_params([term()]) -> boolean().
  1061. valid_params(Values) when is_list(Values) ->
  1062. lists:all(fun is_valid_param/1, Values).
  1063. %% @doc Checks if the given parameter can be encoded for use in the
  1064. %% binary protocol.
  1065. -spec is_valid_param(term()) -> boolean().
  1066. is_valid_param(null) ->
  1067. true;
  1068. is_valid_param(Value) when is_list(Value) ->
  1069. try
  1070. unicode:characters_to_binary(Value)
  1071. of
  1072. Value1 when is_binary(Value1) ->
  1073. true;
  1074. _ErrorOrIncomplete ->
  1075. false
  1076. catch
  1077. error:badarg ->
  1078. false
  1079. end;
  1080. is_valid_param(Value) when is_number(Value) ->
  1081. true;
  1082. is_valid_param(Value) when is_bitstring(Value) ->
  1083. true;
  1084. is_valid_param({Y, M, D}) ->
  1085. is_integer(Y) andalso is_integer(M) andalso is_integer(D);
  1086. is_valid_param({{Y, M, D}, {H, Mi, S}}) ->
  1087. is_integer(Y) andalso is_integer(M) andalso is_integer(D) andalso
  1088. is_integer(H) andalso is_integer(Mi) andalso is_number(S);
  1089. is_valid_param({D, {H, M, S}}) ->
  1090. is_integer(D) andalso
  1091. is_integer(H) andalso is_integer(M) andalso is_number(S);
  1092. is_valid_param(_) ->
  1093. false.
  1094. %% -- Value representation in both the text and binary protocols --
  1095. %% @doc Convert to `<<_:Length/bitstring>>'
  1096. decode_bitstring(Binary, Length) ->
  1097. PaddingLength = bit_size(Binary) - Length,
  1098. <<_:PaddingLength/bitstring, Bitstring:Length/bitstring>> = Binary,
  1099. Bitstring.
  1100. encode_bitstring(Bitstring) ->
  1101. Size = bit_size(Bitstring),
  1102. PaddingSize = byte_size(Bitstring) * 8 - Size,
  1103. <<0:PaddingSize, Bitstring:Size/bitstring>>.
  1104. decode_decimal(Bin, _P, 0) ->
  1105. binary_to_integer(Bin);
  1106. decode_decimal(Bin, P, S) when P =< 15, S > 0 ->
  1107. binary_to_float(Bin);
  1108. decode_decimal(Bin, P, S) when P >= 16, S > 0 ->
  1109. Bin.
  1110. %% -- Protocol basics: packets --
  1111. %% @doc Wraps Data in packet headers, sends it by calling SockModule:send/2 with
  1112. %% Socket and returns {ok, SeqNum1} where SeqNum1 is the next sequence number.
  1113. -spec send_packet(module(), term(), Data :: binary(), SeqNum :: integer()) ->
  1114. {ok, NextSeqNum :: integer()}.
  1115. send_packet(SockModule, Socket, Data, SeqNum) ->
  1116. {WithHeaders, SeqNum1} = add_packet_headers(Data, SeqNum),
  1117. ok = SockModule:send(Socket, WithHeaders),
  1118. {ok, SeqNum1}.
  1119. %% @see recv_packet/4
  1120. recv_packet(SockModule, Socket, SeqNum) ->
  1121. recv_packet(SockModule, Socket, infinity, SeqNum).
  1122. %% @doc Receives data by calling SockModule:recv/2 and removes the packet
  1123. %% headers. Returns the packet contents and the next packet sequence number.
  1124. -spec recv_packet(module(), term(), timeout(), integer() | any) ->
  1125. {ok, Data :: binary(), NextSeqNum :: integer()} | {error, term()}.
  1126. recv_packet(SockModule, Socket, Timeout, SeqNum) ->
  1127. recv_packet(SockModule, Socket, Timeout, SeqNum, <<>>).
  1128. %% @doc Accumulating helper for recv_packet/4
  1129. -spec recv_packet(module(), term(), timeout(), integer() | any, binary()) ->
  1130. {ok, Data :: binary(), NextSeqNum :: integer()} | {error, term()}.
  1131. recv_packet(SockModule, Socket, Timeout, ExpectSeqNum, Acc) ->
  1132. case SockModule:recv(Socket, 4, Timeout) of
  1133. {ok, Header} ->
  1134. {Size, SeqNum, More} = parse_packet_header(Header),
  1135. true = SeqNum == ExpectSeqNum orelse ExpectSeqNum == any,
  1136. {ok, Body} = SockModule:recv(Socket, Size),
  1137. Acc1 = <<Acc/binary, Body/binary>>,
  1138. NextSeqNum = (SeqNum + 1) band 16#ff,
  1139. case More of
  1140. false -> {ok, Acc1, NextSeqNum};
  1141. true -> recv_packet(SockModule, Socket, Timeout, NextSeqNum,
  1142. Acc1)
  1143. end;
  1144. {error, Reason} ->
  1145. {error, Reason}
  1146. end.
  1147. %% @doc Parses a packet header (32 bits) and returns a tuple.
  1148. %%
  1149. %% The client should first read a header and parse it. Then read PacketLength
  1150. %% bytes. If there are more packets, read another header and read a new packet
  1151. %% length of payload until there are no more packets. The seq num should
  1152. %% increment from 0 and may wrap around at 255 back to 0.
  1153. %%
  1154. %% When all packets are read and the payload of all packets are concatenated, it
  1155. %% can be parsed using parse_response/1, etc. depending on what type of response
  1156. %% is expected.
  1157. -spec parse_packet_header(PackerHeader :: binary()) ->
  1158. {PacketLength :: integer(),
  1159. SeqNum :: integer(),
  1160. MorePacketsExist :: boolean()}.
  1161. parse_packet_header(<<PacketLength:24/little-integer, SeqNum:8/integer>>) ->
  1162. {PacketLength, SeqNum, PacketLength == 16#ffffff}.
  1163. %% @doc Splits a packet body into chunks and wraps them in headers. The
  1164. %% resulting list is ready to be sent to the socket. The result is built as a
  1165. %% list to avoid copying large binaries.
  1166. -spec add_packet_headers(Data :: binary(), SeqNum :: integer()) ->
  1167. {PacketsWithHeaders :: iodata(), NextSeqNum :: integer()}.
  1168. add_packet_headers(<<Payload:16#ffffff/binary, Rest/binary>>, SeqNum) ->
  1169. SeqNum1 = (SeqNum + 1) band 16#ff,
  1170. {Packets, NextSeqNum} = add_packet_headers(Rest, SeqNum1),
  1171. Header = <<16#ffffff:24/little, SeqNum:8>>,
  1172. {[Header, Payload | Packets], NextSeqNum};
  1173. add_packet_headers(Bin, SeqNum) when byte_size(Bin) < 16#ffffff ->
  1174. NextSeqNum = (SeqNum + 1) band 16#ff,
  1175. Header = <<(byte_size(Bin)):24/little, SeqNum:8>>,
  1176. {[Header, Bin], NextSeqNum}.
  1177. -spec parse_ok_packet(binary()) -> #ok{}.
  1178. parse_ok_packet(<<?OK:8, Rest/binary>>) ->
  1179. {AffectedRows, Rest1} = lenenc_int(Rest),
  1180. {InsertId, Rest2} = lenenc_int(Rest1),
  1181. <<StatusFlags:16/little, WarningCount:16/little, Msg/binary>> = Rest2,
  1182. %% We have CLIENT_PROTOCOL_41 but not CLIENT_SESSION_TRACK enabled. The
  1183. %% protocol is conditional. This is from the protocol documentation:
  1184. %%
  1185. %% if capabilities & CLIENT_PROTOCOL_41 {
  1186. %% int<2> status_flags
  1187. %% int<2> warning_count
  1188. %% } elseif capabilities & CLIENT_TRANSACTIONS {
  1189. %% int<2> status_flags
  1190. %% }
  1191. %% if capabilities & CLIENT_SESSION_TRACK {
  1192. %% string<lenenc> info
  1193. %% if status_flags & SERVER_SESSION_STATE_CHANGED {
  1194. %% string<lenenc> session_state_changes
  1195. %% }
  1196. %% } else {
  1197. %% string<EOF> info
  1198. %% }
  1199. #ok{affected_rows = AffectedRows,
  1200. insert_id = InsertId,
  1201. status = StatusFlags,
  1202. warning_count = WarningCount,
  1203. msg = Msg}.
  1204. -spec parse_error_packet(binary()) -> #error{}.
  1205. parse_error_packet(<<?ERROR:8, ErrNo:16/little, "#", SQLState:5/binary-unit:8,
  1206. Msg/binary>>) ->
  1207. %% Error, 4.1 protocol.
  1208. %% (Older protocol: <<?ERROR:8, ErrNo:16/little, Msg/binary>>)
  1209. #error{code = ErrNo, state = SQLState, msg = Msg}.
  1210. -spec parse_eof_packet(binary()) -> #eof{}.
  1211. parse_eof_packet(<<?EOF:8, NumWarnings:16/little, StatusFlags:16/little>>) ->
  1212. %% EOF packet, 4.1 protocol.
  1213. %% (Older protocol: <<?EOF:8>>)
  1214. #eof{status = StatusFlags, warning_count = NumWarnings}.
  1215. -spec parse_auth_method_switch(binary()) -> #auth_method_switch{}.
  1216. parse_auth_method_switch(AMSData) ->
  1217. {AuthPluginName, AuthPluginData} = get_null_terminated_binary(AMSData),
  1218. #auth_method_switch{
  1219. auth_plugin_name = AuthPluginName,
  1220. auth_plugin_data = AuthPluginData
  1221. }.
  1222. -spec parse_auth_more_data(binary()) -> auth_more_data().
  1223. parse_auth_more_data(<<3>>) ->
  1224. %% With caching_sha2_password authentication, a single 0x03
  1225. %% byte signals Fast Auth Success.
  1226. fast_auth_completed;
  1227. parse_auth_more_data(<<4>>) ->
  1228. %% With caching_sha2_password authentication, a single 0x04
  1229. %% byte signals a Full Auth Request.
  1230. full_auth_requested;
  1231. parse_auth_more_data(Data) ->
  1232. %% With caching_sha2_password authentication, anything
  1233. %% other than the above should be the public key of the
  1234. %% server.
  1235. PubKey = case public_key:pem_decode(Data) of
  1236. [PemEntry = #'SubjectPublicKeyInfo'{}] ->
  1237. public_key:pem_entry_decode(PemEntry);
  1238. [PemEntry = #'RSAPublicKey'{}] ->
  1239. PemEntry
  1240. end,
  1241. {public_key, PubKey}.
  1242. -spec get_null_terminated_binary(binary()) -> {Binary :: binary(),
  1243. Rest :: binary()}.
  1244. get_null_terminated_binary(In) ->
  1245. get_null_terminated_binary(In, <<>>).
  1246. get_null_terminated_binary(<<0, Rest/binary>>, Acc) ->
  1247. {Acc, Rest};
  1248. get_null_terminated_binary(<<Ch, Rest/binary>>, Acc) ->
  1249. get_null_terminated_binary(Rest, <<Acc/binary, Ch>>).
  1250. -spec hash_password(AuthMethod, Password, Salt) -> Hash
  1251. when AuthMethod :: binary(),
  1252. Password :: iodata(),
  1253. Salt :: binary(),
  1254. Hash :: binary().
  1255. hash_password(AuthMethod, Password, Salt) when not is_binary(Password) ->
  1256. hash_password(AuthMethod, iolist_to_binary(Password), Salt);
  1257. hash_password(?authmethod_none, Password, Salt) ->
  1258. hash_password(?authmethod_mysql_native_password, Password, Salt);
  1259. hash_password(?authmethod_mysql_native_password, <<>>, _Salt) ->
  1260. <<>>;
  1261. hash_password(?authmethod_mysql_native_password, Password, Salt) ->
  1262. %% From the "MySQL Internals" manual:
  1263. %% SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat>
  1264. %% SHA1( SHA1( password ) ) )
  1265. Salt1 = trim_salt(Salt),
  1266. <<Hash1Num:160>> = Hash1 = crypto:hash(sha, Password),
  1267. Hash2 = crypto:hash(sha, Hash1),
  1268. <<Hash3Num:160>> = crypto:hash(sha, <<Salt1/binary, Hash2/binary>>),
  1269. <<(Hash1Num bxor Hash3Num):160>>;
  1270. hash_password(?authmethod_caching_sha2_password, <<>>, _Salt) ->
  1271. <<>>;
  1272. hash_password(?authmethod_caching_sha2_password, Password, Salt) ->
  1273. %% From https://dev.mysql.com/doc/dev/mysql-server/latest/page_caching_sha2_authentication_exchanges.html
  1274. %% (transcribed):
  1275. %% SHA256( password ) XOR SHA256( SHA256( SHA256( password ) ) <concat>
  1276. %% "20-bytes random data from server" )
  1277. Salt1 = trim_salt(Salt),
  1278. <<Hash1Num:256>> = Hash1 = crypto:hash(sha256, Password),
  1279. Hash2 = crypto:hash(sha256, Hash1),
  1280. <<Hash3Num:256>> = crypto:hash(sha256, <<Hash2/binary, Salt1/binary>>),
  1281. <<(Hash1Num bxor Hash3Num):256>>;
  1282. hash_password(?authmethod_sha256_password, Password, Salt) ->
  1283. %% sha256_password authentication is superseded by
  1284. %% caching_sha2_password.
  1285. hash_password(?authmethod_caching_sha2_password, Password, Salt);
  1286. hash_password(UnknownAuthMethod, _, _) ->
  1287. error({auth_method, UnknownAuthMethod}).
  1288. encrypt_password(Password, Salt, PubKey, ServerVersion)
  1289. when is_binary(Password) ->
  1290. %% From http://www.dataarchitect.cloud/preparing-your-community-connector-for-mysql-8-part-2-sha256/:
  1291. %% "The password is "obfuscated" first by employing a rotating "xor" against
  1292. %% the seed bytes that were given to the authentication plugin upon initial
  1293. %% handshake [the auth plugin data].
  1294. %% [...]
  1295. %% Buffer would then be encrypted using the RSA public key the server passed
  1296. %% to the client. The resulting buffer would then be passed back to the
  1297. %% server."
  1298. Salt1 = trim_salt(Salt),
  1299. %% While the article does not mention it, the password must be null-terminated
  1300. %% before obfuscation.
  1301. Password1 = <<Password/binary, 0>>,
  1302. Salt2 = case byte_size(Salt1)<byte_size(Password1) of
  1303. true ->
  1304. binary:copy(Salt1, (byte_size(Password1) div byte_size(Salt1)) + 1);
  1305. false ->
  1306. Salt1
  1307. end,
  1308. Size = bit_size(Password1),
  1309. <<PasswordNum:Size>> = Password1,
  1310. <<SaltNum:Size, _/bitstring>> = Salt2,
  1311. Password2 = <<(PasswordNum bxor SaltNum):Size>>,
  1312. %% From http://www.dataarchitect.cloud/preparing-your-community-connector-for-mysql-8-part-2-sha256/:
  1313. %% "It's important to note that a incompatible change happened in server 8.0.5.
  1314. %% Prior to server 8.0.5 the encryption was done using RSA_PKCS1_PADDING.
  1315. %% With 8.0.5 it is done with RSA_PKCS1_OAEP_PADDING."
  1316. RsaPadding = case ServerVersion < [8, 0, 5] of
  1317. true -> rsa_pkcs1_padding;
  1318. false -> rsa_pkcs1_oaep_padding
  1319. end,
  1320. %% The option rsa_pad was renamed to rsa_padding in OTP/22, but rsa_pad
  1321. %% is being kept for backwards compatibility.
  1322. public_key:encrypt_public(Password2, PubKey, [{rsa_pad, RsaPadding}]);
  1323. encrypt_password(Password, Salt, PubKey, ServerVersion) ->
  1324. encrypt_password(iolist_to_binary(Password), Salt, PubKey, ServerVersion).
  1325. trim_salt(<<SaltNoNul:20/binary-unit:8, 0>>) ->
  1326. SaltNoNul;
  1327. trim_salt(Salt = <<_:20/binary-unit:8>>) ->
  1328. Salt.
  1329. %% --- Lowlevel: variable length integers and strings ---
  1330. %% lenenc_int/1 decodes length-encoded-integer values
  1331. -spec lenenc_int(Input :: binary()) -> {Value :: integer(), Rest :: binary()}.
  1332. lenenc_int(<<Value:8, Rest/bits>>) when Value < 251 -> {Value, Rest};
  1333. lenenc_int(<<16#fc:8, Value:16/little, Rest/binary>>) -> {Value, Rest};
  1334. lenenc_int(<<16#fd:8, Value:24/little, Rest/binary>>) -> {Value, Rest};
  1335. lenenc_int(<<16#fe:8, Value:64/little, Rest/binary>>) -> {Value, Rest}.
  1336. %% Length-encoded-integer encode. Appends the encoded value to Acc.
  1337. %% Values not representable in 64 bits are not accepted.
  1338. -spec lenenc_int_encode(0..16#ffffffffffffffff) -> binary().
  1339. lenenc_int_encode(Value) when Value >= 0 ->
  1340. if Value < 251 -> <<Value>>;
  1341. Value =< 16#ffff -> <<16#fc, Value:16/little>>;
  1342. Value =< 16#ffffff -> <<16#fd, Value:24/little>>;
  1343. Value =< 16#ffffffffffffffff -> <<16#fe, Value:64/little>>
  1344. end.
  1345. %% lenenc_str/1 decodes length-encoded-string values
  1346. -spec lenenc_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
  1347. lenenc_str(Bin) ->
  1348. {Length, Rest} = lenenc_int(Bin),
  1349. <<String:Length/binary, Rest1/binary>> = Rest,
  1350. {String, Rest1}.
  1351. %% Length-encoded-string encode. Prefixes the value with a
  1352. %% length-encoded-integer denoting its size.
  1353. -spec lenenc_str_encode(Input :: binary()) -> binary().
  1354. lenenc_str_encode(Bin) ->
  1355. Length = byte_size(Bin),
  1356. <<(lenenc_int_encode(Length))/binary, Bin:Length/binary>>.
  1357. %% nts/1 decodes a nul-terminated string
  1358. -spec nulterm_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
  1359. nulterm_str(Bin) ->
  1360. [String, Rest] = binary:split(Bin, <<0>>),
  1361. {String, Rest}.
  1362. -ifdef(TEST).
  1363. -include_lib("eunit/include/eunit.hrl").
  1364. %% Testing some of the internal functions, mostly the cases we don't cover in
  1365. %% other tests.
  1366. decode_text_test() ->
  1367. %% Int types
  1368. lists:foreach(fun (T) ->
  1369. ?assertEqual(1, decode_text(#col{type = T}, <<"1">>))
  1370. end,
  1371. [?TYPE_TINY, ?TYPE_SHORT, ?TYPE_LONG, ?TYPE_LONGLONG,
  1372. ?TYPE_INT24, ?TYPE_YEAR]),
  1373. %% BIT
  1374. <<217>> = decode_text(#col{type = ?TYPE_BIT, length = 8}, <<217>>),
  1375. %% Floating point and decimal numbers
  1376. lists:foreach(fun (T) ->
  1377. ?assertEqual(3.0, decode_text(#col{type = T}, <<"3.0">>))
  1378. end,
  1379. [?TYPE_FLOAT, ?TYPE_DOUBLE]),
  1380. %% Decimal types
  1381. lists:foreach(fun (T) ->
  1382. ColDef = #col{type = T, decimals = 1, length = 4},
  1383. ?assertMatch(3.0, decode_text(ColDef, <<"3.0">>))
  1384. end,
  1385. [?TYPE_DECIMAL, ?TYPE_NEWDECIMAL]),
  1386. ?assertEqual(3.0, decode_text(#col{type = ?TYPE_FLOAT}, <<"3">>)),
  1387. ?assertEqual(30.0, decode_text(#col{type = ?TYPE_FLOAT}, <<"3e1">>)),
  1388. ?assertEqual(3, decode_text(#col{type = ?TYPE_LONG}, <<"3">>)),
  1389. %% Date and time
  1390. ?assertEqual({2014, 11, 01},
  1391. decode_text(#col{type = ?TYPE_DATE}, <<"2014-11-01">>)),
  1392. ?assertEqual({0, {23, 59, 01}},
  1393. decode_text(#col{type = ?TYPE_TIME}, <<"23:59:01">>)),
  1394. ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
  1395. decode_text(#col{type = ?TYPE_DATETIME},
  1396. <<"2014-11-01 23:59:01">>)),
  1397. ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
  1398. decode_text(#col{type = ?TYPE_TIMESTAMP},
  1399. <<"2014-11-01 23:59:01">>)),
  1400. %% Strings and blobs
  1401. lists:foreach(fun (T) ->
  1402. ColDef = #col{type = T},
  1403. ?assertEqual(<<"x">>, decode_text(ColDef, <<"x">>))
  1404. end,
  1405. [?TYPE_VARCHAR, ?TYPE_ENUM, ?TYPE_TINY_BLOB,
  1406. ?TYPE_MEDIUM_BLOB, ?TYPE_LONG_BLOB, ?TYPE_BLOB,
  1407. ?TYPE_VAR_STRING, ?TYPE_STRING, ?TYPE_GEOMETRY]),
  1408. ok.
  1409. decode_binary_test() ->
  1410. %% Test the special rounding we apply to (single precision) floats.
  1411. ?assertEqual({1.0, <<>>},
  1412. decode_binary(#col{type = ?TYPE_FLOAT},
  1413. <<1.0:32/float-little>>)),
  1414. ?assertEqual({0.2, <<>>},
  1415. decode_binary(#col{type = ?TYPE_FLOAT},
  1416. <<0.2:32/float-little>>)),
  1417. ?assertEqual({-33.3333, <<>>},
  1418. decode_binary(#col{type = ?TYPE_FLOAT},
  1419. <<-33.333333:32/float-little>>)),
  1420. ?assertEqual({0.000123457, <<>>},
  1421. decode_binary(#col{type = ?TYPE_FLOAT},
  1422. <<0.00012345678:32/float-little>>)),
  1423. ?assertEqual({1234.57, <<>>},
  1424. decode_binary(#col{type = ?TYPE_FLOAT},
  1425. <<1234.56789:32/float-little>>)),
  1426. ok.
  1427. null_bitmap_test() ->
  1428. ?assertEqual({<<0, 1:1>>, <<>>}, null_bitmap_decode(9, <<0, 4>>, 2)),
  1429. ?assertEqual(<<0, 4>>, null_bitmap_encode(<<0, 1:1>>, 2)),
  1430. ok.
  1431. lenenc_int_test() ->
  1432. %% decode
  1433. ?assertEqual({40, <<>>}, lenenc_int(<<40>>)),
  1434. ?assertEqual({16#ff, <<>>}, lenenc_int(<<16#fc, 255, 0>>)),
  1435. ?assertEqual({16#33aaff, <<>>}, lenenc_int(<<16#fd, 16#ff, 16#aa, 16#33>>)),
  1436. ?assertEqual({16#12345678, <<>>}, lenenc_int(<<16#fe, 16#78, 16#56, 16#34,
  1437. 16#12, 0, 0, 0, 0>>)),
  1438. %% encode
  1439. ?assertEqual(<<40>>, lenenc_int_encode(40)),
  1440. ?assertEqual(<<16#fc, 255, 0>>, lenenc_int_encode(255)),
  1441. ?assertEqual(<<16#fd, 16#ff, 16#aa, 16#33>>,
  1442. lenenc_int_encode(16#33aaff)),
  1443. ?assertEqual(<<16#fe, 16#78, 16#56, 16#34, 16#12, 0, 0, 0, 0>>,
  1444. lenenc_int_encode(16#12345678)),
  1445. ok.
  1446. lenenc_str_test() ->
  1447. ?assertEqual({<<"Foo">>, <<"bar">>}, lenenc_str(<<3, "Foobar">>)).
  1448. nulterm_test() ->
  1449. ?assertEqual({<<"Foo">>, <<"bar">>}, nulterm_str(<<"Foo", 0, "bar">>)).
  1450. parse_header_test() ->
  1451. %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
  1452. Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
  1453. <<Header:4/binary-unit:8, Body/binary>> = Packet,
  1454. %% Check header contents and body length
  1455. ?assertEqual({size(Body), 5, false}, parse_packet_header(Header)),
  1456. ok.
  1457. add_packet_headers_test() ->
  1458. {Data, 43} = add_packet_headers(<<"foo">>, 42),
  1459. ?assertEqual(<<3, 0, 0, 42, "foo">>, list_to_binary(Data)).
  1460. add_packet_headers_equal_to_0xffffff_test() ->
  1461. BigBin = binary:copy(<<1>>, 16#ffffff),
  1462. {Data, 44} = add_packet_headers(BigBin, 42),
  1463. ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary,
  1464. 0, 0, 0, 43>>,
  1465. list_to_binary(Data)).
  1466. add_packet_headers_greater_than_0xffffff_test() ->
  1467. BigBin = binary:copy(<<1>>, 16#ffffff),
  1468. {Data, 44} = add_packet_headers(<<BigBin/binary, "foo">>, 42),
  1469. ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary, 3, 0, 0, 43, "foo">>,
  1470. list_to_binary(Data)).
  1471. add_packet_headers_2_times_greater_than_0xffffff_test() ->
  1472. BigBin = binary:copy(<<1>>, 16#ffffff),
  1473. {Data, 45} = add_packet_headers(<<BigBin/binary, BigBin/binary, "foo">>, 42),
  1474. ?assertEqual(<<16#ff, 16#ff, 16#ff, 42, BigBin/binary,
  1475. 16#ff, 16#ff, 16#ff, 43, BigBin/binary,
  1476. 3, 0, 0, 44, "foo">>,
  1477. list_to_binary(Data)).
  1478. parse_ok_test() ->
  1479. Body = <<0, 5, 1, 2, 0, 0, 0, "Foo">>,
  1480. ?assertEqual(#ok{affected_rows = 5,
  1481. insert_id = 1,
  1482. status = ?SERVER_STATUS_AUTOCOMMIT,
  1483. warning_count = 0,
  1484. msg = <<"Foo">>},
  1485. parse_ok_packet(Body)).
  1486. parse_error_test() ->
  1487. %% Protocol 4.1
  1488. Body = <<255, 42, 0, "#", "XYZxx", "Foo">>,
  1489. ?assertEqual(#error{code = 42, state = <<"XYZxx">>, msg = <<"Foo">>},
  1490. parse_error_packet(Body)),
  1491. ok.
  1492. parse_eof_test() ->
  1493. %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
  1494. Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
  1495. <<_Header:4/binary-unit:8, Body/binary>> = Packet,
  1496. %% Ignore header. Parse body as an eof_packet.
  1497. ?assertEqual(#eof{warning_count = 0,
  1498. status = ?SERVER_STATUS_AUTOCOMMIT},
  1499. parse_eof_packet(Body)),
  1500. ok.
  1501. hash_password_test() ->
  1502. ?assertEqual(<<222,207,222,139,41,181,202,13,191,241,
  1503. 234,234,73,127,244,101,205,3,28,251>>,
  1504. hash_password(?authmethod_mysql_native_password,
  1505. <<"foo">>, <<"abcdefghijklmnopqrst">>)),
  1506. ?assertEqual(<<>>, hash_password(?authmethod_mysql_native_password,
  1507. <<>>, <<"abcdefghijklmnopqrst">>)),
  1508. ?assertEqual(<<125,155,142,2,20,139,6,254,65,126,239,
  1509. 146,107,77,17,8,120,55,247,33,87,16,76,
  1510. 63,128,131,60,188,58,81,171,242>>,
  1511. hash_password(?authmethod_caching_sha2_password,
  1512. <<"foo">>, <<"abcdefghijklmnopqrst">>)),
  1513. ?assertEqual(<<>>, hash_password(?authmethod_caching_sha2_password,
  1514. <<>>, <<"abcdefghijklmnopqrst">>)).
  1515. valid_params_test() ->
  1516. ValidParams = [
  1517. null,
  1518. 1,
  1519. 0.5,
  1520. <<>>, <<$x>>, <<0:1>>,
  1521. %% valid unicode
  1522. [], [$x], [16#E4],
  1523. %% valid date
  1524. {1, 2, 3},
  1525. %% valid time
  1526. {1, {2, 3, 4}}, {1, {2, 3, 4.5}},
  1527. %% valid datetime
  1528. {{1, 2, 3}, {4, 5, 6}}, {{1, 2, 3}, {4, 5, 6.5}}
  1529. ],
  1530. InvalidParams = [
  1531. x,
  1532. [x],
  1533. {},
  1534. self(),
  1535. make_ref(),
  1536. fun () -> ok end,
  1537. %% invalid unicode
  1538. [16#FFFFFFFF],
  1539. %% invalid date
  1540. {x, 1, 2}, {1, x, 2}, {1, 2, x},
  1541. %% invalid time
  1542. {x, {1, 2, 3}}, {1, {x, 2, 3}},
  1543. {1, {2, x, 3}}, {1, {2, 3, x}},
  1544. %% invalid datetime
  1545. {{x, 1, 2}, {3, 4, 5}}, {{1, x, 2}, {3, 4, 5}},
  1546. {{1, 2, x}, {3, 4, 5}}, {{1, 2, 3}, {x, 4, 5}},
  1547. {{1, 2, 3}, {4, x, 5}}, {{1, 2, 3}, {4, 5, x}}
  1548. ],
  1549. lists:foreach(
  1550. fun (ValidParam) ->
  1551. ?assert(is_valid_param(ValidParam))
  1552. end,
  1553. ValidParams),
  1554. ?assert(valid_params(ValidParams)),
  1555. lists:foreach(
  1556. fun (InvalidParam) ->
  1557. ?assertNot(is_valid_param(InvalidParam))
  1558. end,
  1559. InvalidParams),
  1560. ?assertNot(valid_params(InvalidParams)),
  1561. ?assertNot(valid_params(ValidParams ++ InvalidParams)).
  1562. -endif.