mysql_protocol.erl 44 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. %% MySQL/OTP – MySQL client library for Erlang/OTP
  2. %% Copyright (C) 2014 Viktor Söderqvist
  3. %%
  4. %% This file is part of MySQL/OTP.
  5. %%
  6. %% MySQL/OTP is free software: you can redistribute it and/or modify it under
  7. %% the terms of the GNU Lesser General Public License as published by the Free
  8. %% Software Foundation, either version 3 of the License, or (at your option)
  9. %% any later version.
  10. %%
  11. %% This program is distributed in the hope that it will be useful, but WITHOUT
  12. %% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13. %% FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
  14. %% more details.
  15. %%
  16. %% You should have received a copy of the GNU Lesser General Public License
  17. %% along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. %% @doc This module implements parts of the MySQL client/server protocol.
  19. %%
  20. %% The protocol is described in the document "MySQL Internals" which can be
  21. %% found under "MySQL Documentation: Expert Guides" on http://dev.mysql.com/.
  22. %%
  23. %% TCP communication is not handled in this module. Most of the public functions
  24. %% take funs for data communitaction as parameters.
  25. -module(mysql_protocol).
  26. -export([handshake/5,
  27. query/3,
  28. prepare/3, unprepare/3, execute/4]).
  29. -export_type([sendfun/0, recvfun/0]).
  30. -type sendfun() :: fun((binary()) -> ok).
  31. -type recvfun() :: fun((integer()) -> {ok, binary()}).
  32. %% How much data do we want to send at most?
  33. -define(MAX_BYTES_PER_PACKET, 50000000).
  34. -include("records.hrl").
  35. -include("protocol.hrl").
  36. %% Macros for pattern matching on packets.
  37. -define(ok_pattern, <<?OK, _/binary>>).
  38. -define(error_pattern, <<?ERROR, _/binary>>).
  39. -define(eof_pattern, <<?EOF, _:4/binary>>).
  40. %% @doc Performs a handshake using the supplied functions for communication.
  41. %% Returns an ok or an error record. Raises errors when various unimplemented
  42. %% features are requested.
  43. %%
  44. %% TODO: Implement setting the database in the handshake. Currently an error
  45. %% occurs if Database is anything other than undefined.
  46. -spec handshake(iodata(), iodata(), iodata() | undefined, sendfun(),
  47. recvfun()) -> #ok{} | #error{}.
  48. handshake(Username, Password, Database, SendFun, RecvFun) ->
  49. SeqNum0 = 0,
  50. Database == undefined orelse error(database_in_handshake),
  51. {ok, HandshakePacket, SeqNum1} = recv_packet(RecvFun, SeqNum0),
  52. Handshake = parse_handshake(HandshakePacket),
  53. Response = build_handshake_response(Handshake, Username, Password),
  54. {ok, SeqNum2} = send_packet(SendFun, Response, SeqNum1),
  55. {ok, ConfirmPacket, _SeqNum3} = recv_packet(RecvFun, SeqNum2),
  56. parse_handshake_confirm(ConfirmPacket).
  57. -spec query(Query :: iodata(), sendfun(), recvfun()) ->
  58. #ok{} | #error{} | #resultset{}.
  59. query(Query, SendFun, RecvFun) ->
  60. Req = <<?COM_QUERY, (iolist_to_binary(Query))/binary>>,
  61. SeqNum0 = 0,
  62. {ok, SeqNum1} = send_packet(SendFun, Req, SeqNum0),
  63. {ok, Resp, SeqNum2} = recv_packet(RecvFun, SeqNum1),
  64. case Resp of
  65. ?ok_pattern ->
  66. parse_ok_packet(Resp);
  67. ?error_pattern ->
  68. parse_error_packet(Resp);
  69. _ResultSet ->
  70. %% The first packet in a resultset is only the column count.
  71. {ColumnCount, <<>>} = lenenc_int(Resp),
  72. case fetch_resultset(RecvFun, ColumnCount, SeqNum2) of
  73. #error{} = E ->
  74. E;
  75. #resultset{column_definitions = ColDefs, rows = Rows} = R ->
  76. %% Parse the rows according to the 'text protocol'
  77. %% representation.
  78. ColumnTypes = [ColDef#column_definition.type
  79. || ColDef <- ColDefs],
  80. Rows1 = [decode_text_row(ColumnCount, ColumnTypes, Row)
  81. || Row <- Rows],
  82. R#resultset{rows = Rows1}
  83. end
  84. end.
  85. %% @doc Prepares a statement.
  86. -spec prepare(iodata(), sendfun(), recvfun()) -> #error{} | #prepared{}.
  87. prepare(Query, SendFun, RecvFun) ->
  88. Req = <<?COM_STMT_PREPARE, (iolist_to_binary(Query))/binary>>,
  89. {ok, SeqNum1} = send_packet(SendFun, Req, 0),
  90. {ok, Resp, SeqNum2} = recv_packet(RecvFun, SeqNum1),
  91. case Resp of
  92. ?error_pattern ->
  93. parse_error_packet(Resp);
  94. <<?OK,
  95. StmtId:32/little,
  96. NumColumns:16/little,
  97. NumParams:16/little,
  98. 0, %% reserved_1 -- [00] filler
  99. WarningCount:16/little>> ->
  100. %% This was the first packet.
  101. %% Now: Parameter Definition Block. The parameter definitions don't
  102. %% contain any useful data at all. They are always TYPE_VAR_STRING
  103. %% with charset 'binary' so we have to select a type ourselves for
  104. %% the parameters we have in execute/4.
  105. {_ParamDefs, SeqNum3} =
  106. fetch_column_definitions_if_any(NumParams, RecvFun, SeqNum2),
  107. %% Column Definition Block. We get column definitions in execute
  108. %% too, so we don't need them here. We *could* store them to be able
  109. %% to provide the user with some info about a prepared statement.
  110. {_ColDefs, _SeqNum4} =
  111. fetch_column_definitions_if_any(NumColumns, RecvFun, SeqNum3),
  112. #prepared{statement_id = StmtId,
  113. param_count = NumParams,
  114. warning_count = WarningCount}
  115. end.
  116. %% @doc Deallocates a prepared statement.
  117. -spec unprepare(#prepared{}, sendfun(), recvfun()) -> ok.
  118. unprepare(#prepared{statement_id = Id}, SendFun, _RecvFun) ->
  119. {ok, _SeqNum} = send_packet(SendFun, <<?COM_STMT_CLOSE, Id:32/little>>, 0),
  120. ok.
  121. %% @doc Executes a prepared statement.
  122. -spec execute(#prepared{}, [term()], sendfun(), recvfun()) -> #resultset{}.
  123. execute(#prepared{statement_id = Id, param_count = ParamCount}, ParamValues,
  124. SendFun, RecvFun) when ParamCount == length(ParamValues) ->
  125. %% Flags Constant Name
  126. %% 0x00 CURSOR_TYPE_NO_CURSOR
  127. %% 0x01 CURSOR_TYPE_READ_ONLY
  128. %% 0x02 CURSOR_TYPE_FOR_UPDATE
  129. %% 0x04 CURSOR_TYPE_SCROLLABLE
  130. Flags = 0,
  131. Req0 = <<?COM_STMT_EXECUTE, Id:32/little, Flags, 1:32/little>>,
  132. Req = case ParamCount of
  133. 0 ->
  134. Req0;
  135. _ ->
  136. %% We can't use the parameter types returned by the prepare call.
  137. %% They are all reported as ?TYPE_VAR_STRING with character
  138. %% set 'binary'.
  139. NullBitMap = build_null_bitmap(ParamValues),
  140. %% What does it mean to *not* bind new params? To use the same
  141. %% params as last time? Right now we always bind params each time.
  142. NewParamsBoundFlag = 1,
  143. Req1 = <<Req0/binary, NullBitMap/binary, NewParamsBoundFlag>>,
  144. %% For each value, first append type and signedness (16#80 signed or
  145. %% 00 unsigned) for all values and then the binary encoded values.
  146. EncodedParams = lists:map(fun encode_param/1, ParamValues),
  147. {TypesAndSigns, EncValues} = lists:unzip(EncodedParams),
  148. iolist_to_binary([Req1, TypesAndSigns, EncValues])
  149. end,
  150. {ok, SeqNum1} = send_packet(SendFun, Req, 0),
  151. {ok, Resp, SeqNum2} = recv_packet(RecvFun, SeqNum1),
  152. case Resp of
  153. ?ok_pattern ->
  154. parse_ok_packet(Resp);
  155. ?error_pattern ->
  156. parse_error_packet(Resp);
  157. _ResultPacket ->
  158. %% The first packet in a resultset is only the column count.
  159. {ColumnCount, <<>>} = lenenc_int(Resp),
  160. case fetch_resultset(RecvFun, ColumnCount, SeqNum2) of
  161. #error{} = E ->
  162. %% TODO: Find a way to get here and write a testcase.
  163. %% This can happen for the text protocol but maybe not for
  164. %% the binary protocol.
  165. E;
  166. #resultset{column_definitions = ColDefs, rows = Rows} = R ->
  167. %% Parse the rows according to the 'binary protocol'
  168. %% representation.
  169. ColumnTypes = [ColDef#column_definition.type
  170. || ColDef <- ColDefs],
  171. Rows1 = [decode_binary_row(ColumnCount, ColumnTypes, Row)
  172. || Row <- Rows],
  173. R#resultset{rows = Rows1}
  174. end
  175. end.
  176. %% --- internal ---
  177. %% @doc Parses a handshake. This is the first thing that comes from the server
  178. %% when connecting. If an unsupported version or variant of the protocol is used
  179. %% an error is raised.
  180. -spec parse_handshake(binary()) -> #handshake{}.
  181. parse_handshake(<<10, Rest/binary>>) ->
  182. %% Protocol version 10.
  183. {ServerVersion, Rest1} = nulterm_str(Rest),
  184. <<ConnectionId:32/little,
  185. AuthPluginDataPart1:8/binary-unit:8,
  186. 0, %% "filler" -- everything below is optional
  187. CapabilitiesLower:16/little,
  188. CharacterSet:8,
  189. StatusFlags:16/little,
  190. CapabilitiesUpper:16/little,
  191. AuthPluginDataLength:8, %% if cabab & CLIENT_PLUGIN_AUTH, otherwise 0
  192. _Reserved:10/binary-unit:8, %% 10 unused (reserved) bytes
  193. Rest3/binary>> = Rest1,
  194. Capabilities = CapabilitiesLower + 16#10000 * CapabilitiesUpper,
  195. Len = case AuthPluginDataLength of
  196. 0 -> 13; %% Server has not CLIENT_PLUGIN_AUTH
  197. K -> K - 8 %% Part 2 length = Total length minus the 8 bytes in part 1.
  198. end,
  199. <<AuthPluginDataPart2:Len/binary-unit:8, AuthPluginName/binary>> = Rest3,
  200. AuthPluginData = <<AuthPluginDataPart1/binary, AuthPluginDataPart2/binary>>,
  201. %% "Due to Bug#59453 the auth-plugin-name is missing the terminating
  202. %% NUL-char in versions prior to 5.5.10 and 5.6.2."
  203. %% Strip the final NUL byte if any.
  204. NameLen = size(AuthPluginName) - 1,
  205. AuthPluginName1 = case AuthPluginName of
  206. <<NameNoNul:NameLen/binary-unit:8, 0>> -> NameNoNul;
  207. _ -> AuthPluginName
  208. end,
  209. #handshake{server_version = ServerVersion,
  210. connection_id = ConnectionId,
  211. capabilities = Capabilities,
  212. character_set = CharacterSet,
  213. status = StatusFlags,
  214. auth_plugin_data = AuthPluginData,
  215. auth_plugin_name = AuthPluginName1};
  216. parse_handshake(<<Protocol:8, _/binary>>) when Protocol /= 10 ->
  217. error(unknown_protocol).
  218. %% @doc The response sent by the client to the server after receiving the
  219. %% initial handshake from the server
  220. -spec build_handshake_response(#handshake{}, iodata(), iodata()) -> binary().
  221. build_handshake_response(Handshake, Username, Password) ->
  222. %% We require these capabilities. Make sure the server handles them.
  223. CapabilityFlags = ?CLIENT_PROTOCOL_41 bor
  224. ?CLIENT_TRANSACTIONS bor
  225. ?CLIENT_SECURE_CONNECTION,
  226. Handshake#handshake.capabilities band CapabilityFlags == CapabilityFlags
  227. orelse error(old_server_version),
  228. Hash = case Handshake#handshake.auth_plugin_name of
  229. <<>> ->
  230. %% Server doesn't know auth plugins
  231. hash_password(Password, Handshake#handshake.auth_plugin_data);
  232. <<"mysql_native_password">> ->
  233. hash_password(Password, Handshake#handshake.auth_plugin_data);
  234. UnknownAuthMethod ->
  235. error({auth_method, UnknownAuthMethod})
  236. end,
  237. HashLength = size(Hash),
  238. CharacterSet = ?UTF8,
  239. UsernameUtf8 = unicode:characters_to_binary(Username),
  240. <<CapabilityFlags:32/little,
  241. ?MAX_BYTES_PER_PACKET:32/little,
  242. CharacterSet:8,
  243. 0:23/unit:8, %% reserverd
  244. UsernameUtf8/binary,
  245. 0, %% NUL-terminator for the username
  246. HashLength,
  247. Hash/binary>>.
  248. %% @doc Handles the second packet from the server, when we have replied to the
  249. %% initial handshake. Returns an error if the server returns an error. Raises
  250. %% an error if unimplemented features are required.
  251. -spec parse_handshake_confirm(binary()) -> #ok{} | #error{}.
  252. parse_handshake_confirm(Packet) ->
  253. case Packet of
  254. ?ok_pattern ->
  255. %% Connection complete.
  256. parse_ok_packet(Packet);
  257. ?error_pattern ->
  258. %% "Insufficient Client Capabilities"
  259. parse_error_packet(Packet);
  260. <<?EOF>> ->
  261. %% "Old Authentication Method Switch Request Packet consisting of a
  262. %% single 0xfe byte. It is sent by server to request client to
  263. %% switch to Old Password Authentication if CLIENT_PLUGIN_AUTH
  264. %% capability is not supported (by either the client or the server)"
  265. error(old_auth);
  266. <<?EOF, _/binary>> ->
  267. %% "Authentication Method Switch Request Packet. If both server and
  268. %% client support CLIENT_PLUGIN_AUTH capability, server can send
  269. %% this packet to ask client to use another authentication method."
  270. error(auth_method_switch)
  271. end.
  272. %% Fetches packets until a
  273. -spec fetch_resultset(recvfun(), integer(), integer()) ->
  274. #resultset{} | #error{}.
  275. fetch_resultset(RecvFun, FieldCount, SeqNum) ->
  276. {ok, ColDefs, SeqNum1} = fetch_column_definitions(RecvFun, SeqNum,
  277. FieldCount, []),
  278. {ok, DelimiterPacket, SeqNum2} = recv_packet(RecvFun, SeqNum1),
  279. #eof{} = parse_eof_packet(DelimiterPacket),
  280. case fetch_resultset_rows(RecvFun, SeqNum2, []) of
  281. {ok, Rows, _SeqNum3} ->
  282. #resultset{column_definitions = ColDefs, rows = Rows};
  283. #error{} = E ->
  284. E
  285. end.
  286. %% @doc Receives NumLeft packets and parses them as column definitions.
  287. %% TODO: Don't parse them here. That's a sepatate thing we not always need to
  288. %% do.
  289. -spec fetch_column_definitions(recvfun(), SeqNum :: integer(),
  290. NumLeft :: integer(), Acc :: [tuple()]) ->
  291. {ok, [tuple()], NextSeqNum :: integer()}.
  292. fetch_column_definitions(RecvFun, SeqNum, NumLeft, Acc) when NumLeft > 0 ->
  293. {ok, Packet, SeqNum1} = recv_packet(RecvFun, SeqNum),
  294. ColDef = parse_column_definition(Packet),
  295. fetch_column_definitions(RecvFun, SeqNum1, NumLeft - 1, [ColDef | Acc]);
  296. fetch_column_definitions(_RecvFun, SeqNum, 0, Acc) ->
  297. {ok, lists:reverse(Acc), SeqNum}.
  298. %% @doc Fetches rows in a result set. There is a packet per row. The row packets
  299. %% are not decoded. This function can be used for both the binary and the text
  300. %% protocol result sets.
  301. -spec fetch_resultset_rows(recvfun(), SeqNum :: integer(), Acc) ->
  302. {ok, Rows, integer()} | #error{}
  303. when Acc :: [binary()],
  304. Rows :: [binary()].
  305. fetch_resultset_rows(RecvFun, SeqNum, Acc) ->
  306. {ok, Packet, SeqNum1} = recv_packet(RecvFun, SeqNum),
  307. case Packet of
  308. ?error_pattern ->
  309. parse_error_packet(Packet);
  310. ?eof_pattern ->
  311. {ok, lists:reverse(Acc), SeqNum1};
  312. Row ->
  313. fetch_resultset_rows(RecvFun, SeqNum1, [Row | Acc])
  314. end.
  315. %% -- both text and binary protocol --
  316. %% Parses a packet containing a column definition (part of a result set)
  317. parse_column_definition(Data) ->
  318. {<<"def">>, Rest1} = lenenc_str(Data), %% catalog (always "def")
  319. {_Schema, Rest2} = lenenc_str(Rest1), %% schema-name
  320. {_Table, Rest3} = lenenc_str(Rest2), %% virtual table-name
  321. {_OrgTable, Rest4} = lenenc_str(Rest3), %% physical table-name
  322. {Name, Rest5} = lenenc_str(Rest4), %% virtual column name
  323. {_OrgName, Rest6} = lenenc_str(Rest5), %% physical column name
  324. {16#0c, Rest7} = lenenc_int(Rest6), %% length of the following fields
  325. %% (always 0x0c)
  326. <<Charset:16/little, %% column character set
  327. _ColumnLength:32/little, %% maximum length of the field
  328. ColumnType:8, %% type of the column as defined in Column Type
  329. _Flags:16/little, %% flags
  330. _Decimals:8, %% max shown decimal digits:
  331. 0, %% "filler" %% - 0x00 for integers and static strings
  332. 0, %% - 0x1f for dynamic strings, double, float
  333. Rest8/binary>> = Rest7, %% - 0x00 to 0x51 for decimals
  334. %% Here, if command was COM_FIELD_LIST {
  335. %% default values: lenenc_str
  336. %% }
  337. <<>> = Rest8,
  338. #column_definition{name = Name, type = ColumnType, charset = Charset}.
  339. %% -- text protocol --
  340. -spec decode_text_row(NumColumns :: integer(), ColumnTypes :: integer(),
  341. Data :: binary()) -> [term()].
  342. decode_text_row(_NumColumns, ColumnTypes, Data) ->
  343. decode_text_row_acc(ColumnTypes, Data, []).
  344. %% parses Data using ColDefs and builds the values Acc.
  345. decode_text_row_acc([Type | Types], Data, Acc) ->
  346. case Data of
  347. <<16#fb, Rest/binary>> ->
  348. %% NULL
  349. decode_text_row_acc(Types, Rest, [null | Acc]);
  350. _ ->
  351. %% Every thing except NULL
  352. {Text, Rest} = lenenc_str(Data),
  353. Term = decode_text(Type, Text),
  354. decode_text_row_acc(Types, Rest, [Term | Acc])
  355. end;
  356. decode_text_row_acc([], <<>>, Acc) ->
  357. lists:reverse(Acc).
  358. %% @doc When receiving data in the text protocol, we get everything as binaries
  359. %% (except NULL). This function is used to parse these strings values.
  360. decode_text(_, null) ->
  361. %% NULL is the only value not represented as a binary.
  362. null;
  363. decode_text(T, Text)
  364. when T == ?TYPE_TINY; T == ?TYPE_SHORT; T == ?TYPE_LONG; T == ?TYPE_LONGLONG;
  365. T == ?TYPE_INT24; T == ?TYPE_YEAR; T == ?TYPE_BIT ->
  366. %% For BIT, do we want bitstring, int or binary?
  367. binary_to_integer(Text);
  368. decode_text(T, Text)
  369. when T == ?TYPE_DECIMAL; T == ?TYPE_NEWDECIMAL; T == ?TYPE_VARCHAR;
  370. T == ?TYPE_ENUM; T == ?TYPE_TINY_BLOB; T == ?TYPE_MEDIUM_BLOB;
  371. T == ?TYPE_LONG_BLOB; T == ?TYPE_BLOB; T == ?TYPE_VAR_STRING;
  372. T == ?TYPE_STRING; T == ?TYPE_GEOMETRY ->
  373. Text;
  374. decode_text(?TYPE_DATE, <<Y:4/binary, "-", M:2/binary, "-", D:2/binary>>) ->
  375. {binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)};
  376. decode_text(?TYPE_TIME, Text) ->
  377. {match, [Sign, Hbin, Mbin, Sbin, Frac]} =
  378. re:run(Text,
  379. <<"^(-?)(\\d+):(\\d+):(\\d+)(\\.?\\d*)$">>,
  380. [{capture, all_but_first, binary}]),
  381. H = binary_to_integer(Hbin),
  382. M = binary_to_integer(Mbin),
  383. S = binary_to_integer(Sbin),
  384. IsNeg = Sign == <<"-">>,
  385. Fraction = case Frac of
  386. <<>> -> 0;
  387. _ when not IsNeg -> binary_to_float(<<"0", Frac/binary>>);
  388. _ when IsNeg -> 1 - binary_to_float(<<"0", Frac/binary>>)
  389. end,
  390. Sec1 = H * 3600 + M * 60 + S,
  391. Sec2 = if IsNeg -> -Sec1; true -> Sec1 end,
  392. Sec3 = if IsNeg and (Fraction /= 0) -> Sec2 - 1;
  393. true -> Sec2
  394. end,
  395. {Days, {Hours, Minutes, Seconds}} = calendar:seconds_to_daystime(Sec3),
  396. {Days, {Hours, Minutes, Seconds + Fraction}};
  397. decode_text(T, <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
  398. H:2/binary, ":", Mi:2/binary, ":", S:2/binary>>)
  399. when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
  400. %% Without fractions.
  401. {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
  402. {binary_to_integer(H), binary_to_integer(Mi), binary_to_integer(S)}};
  403. decode_text(T, <<Y:4/binary, "-", M:2/binary, "-", D:2/binary, " ",
  404. H:2/binary, ":", Mi:2/binary, ":", FloatS/binary>>)
  405. when T == ?TYPE_TIMESTAMP; T == ?TYPE_DATETIME ->
  406. %% With fractions.
  407. {{binary_to_integer(Y), binary_to_integer(M), binary_to_integer(D)},
  408. {binary_to_integer(H), binary_to_integer(Mi), binary_to_float(FloatS)}};
  409. decode_text(T, Text) when T == ?TYPE_FLOAT; T == ?TYPE_DOUBLE ->
  410. try binary_to_float(Text)
  411. catch error:badarg ->
  412. try binary_to_integer(Text) of
  413. Int -> float(Int)
  414. catch error:badarg ->
  415. %% It is something like "4e75" that must be turned into "4.0e75"
  416. binary_to_float(binary:replace(Text, <<"e">>, <<".0e">>))
  417. end
  418. end;
  419. decode_text(?TYPE_SET, <<>>) ->
  420. sets:new();
  421. decode_text(?TYPE_SET, Text) ->
  422. sets:from_list(binary:split(Text, <<",">>, [global])).
  423. %% -- binary protocol --
  424. %% @doc If NumColumns is non-zero, fetches this number of column definitions
  425. %% and an EOF packet. Used by prepare/3.
  426. fetch_column_definitions_if_any(0, _RecvFun, SeqNum) ->
  427. {[], SeqNum};
  428. fetch_column_definitions_if_any(N, RecvFun, SeqNum) ->
  429. {ok, Defs, SeqNum1} = fetch_column_definitions(RecvFun, SeqNum, N, []),
  430. {ok, ?eof_pattern, SeqNum2} = recv_packet(RecvFun, SeqNum1),
  431. {Defs, SeqNum2}.
  432. %% @doc Decodes a packet representing a row in a binary result set.
  433. %% It consists of a 0 byte, then a null bitmap, then the values.
  434. %% Returns a list of length NumColumns with terms of appropriate types for each
  435. %% MySQL type in ColumnTypes.
  436. -spec decode_binary_row(NumColumns :: integer(), ColumnTypes :: [integer()],
  437. Data :: binary()) -> [term()].
  438. decode_binary_row(NumColumns, ColumnTypes, <<0, Data/binary>>) ->
  439. {NullBitMap, Rest} = null_bitmap_decode(NumColumns, Data, 2),
  440. decode_binary_row_acc(ColumnTypes, NullBitMap, Rest, []).
  441. %% @doc Accumulating helper for decode_binary_row/3.
  442. decode_binary_row_acc([_ | Types], <<1:1, NullBitMap/bitstring>>, Data, Acc) ->
  443. %% NULL
  444. decode_binary_row_acc(Types, NullBitMap, Data, [null | Acc]);
  445. decode_binary_row_acc([Type | Types], <<0:1, NullBitMap/bitstring>>, Data,
  446. Acc) ->
  447. %% Not NULL
  448. {Term, Rest} = decode_binary(Type, Data),
  449. decode_binary_row_acc(Types, NullBitMap, Rest, [Term | Acc]);
  450. decode_binary_row_acc([], _, <<>>, Acc) ->
  451. lists:reverse(Acc).
  452. %% @doc Decodes a null bitmap as stored by MySQL and returns it in a strait
  453. %% bitstring counting bits from left to right in a tuple with remaining data.
  454. %%
  455. %% In the MySQL null bitmap the bits are stored counting bytes from the left and
  456. %% bits within each byte from the right. (Sort of little endian.)
  457. -spec null_bitmap_decode(NumColumns :: integer(), BitOffset :: integer(),
  458. Data :: binary()) ->
  459. {NullBitstring :: bitstring(), Rest :: binary()}.
  460. null_bitmap_decode(NumColumns, Data, BitOffset) ->
  461. %% Binary shift right by 3 is equivallent to integer division by 8.
  462. BitMapLength = (NumColumns + BitOffset + 7) bsr 3,
  463. <<NullBitstring0:BitMapLength/binary, Rest/binary>> = Data,
  464. <<_:BitOffset, NullBitstring:NumColumns/bitstring, _/bitstring>> =
  465. << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= NullBitstring0 >>,
  466. {NullBitstring, Rest}.
  467. %% @doc The reverse of null_bitmap_decode/3. The number of columns is taken to
  468. %% be the number of bits in NullBitstring. Returns the MySQL null bitmap as a
  469. %% binary (i.e. full bytes). BitOffset is the number of unused bits that should
  470. %% be inserted before the other bits.
  471. -spec null_bitmap_encode(bitstring(), integer()) -> binary().
  472. null_bitmap_encode(NullBitstring, BitOffset) ->
  473. PayloadLength = bit_size(NullBitstring) + BitOffset,
  474. %% Round up to a multiple of 8.
  475. BitMapLength = (PayloadLength + 7) band bnot 7,
  476. PadBitsLength = BitMapLength - PayloadLength,
  477. PaddedBitstring = <<0:BitOffset, NullBitstring/bitstring, 0:PadBitsLength>>,
  478. << <<(reverse_byte(B))/binary>> || <<B:1/binary>> <= PaddedBitstring >>.
  479. %% Reverses the bits in a byte.
  480. reverse_byte(<<A:1, B:1, C:1, D:1, E:1, F:1, G:1, H:1>>) ->
  481. <<H:1, G:1, F:1, E:1, D:1, C:1, B:1, A:1>>.
  482. %% @doc Used for executing prepared statements. The bit offset whould be 0 in
  483. %% this case.
  484. -spec build_null_bitmap([any()]) -> binary().
  485. build_null_bitmap(Values) ->
  486. Bits = << <<(case V of null -> 1; _ -> 0 end):1>> || V <- Values >>,
  487. null_bitmap_encode(Bits, 0).
  488. %% Decodes a value as received in the 'binary protocol' result set.
  489. %%
  490. %% The types are type constants for the binary protocol, such as
  491. %% ProtocolBinary::MYSQL_TYPE_STRING. In the guide "MySQL Internals" these are
  492. %% not listed, but we assume that are the same as for the text protocol.
  493. -spec decode_binary(Type :: integer(), Data :: binary()) ->
  494. {Term :: term(), Rest :: binary()}.
  495. decode_binary(T, Data)
  496. when T == ?TYPE_STRING; T == ?TYPE_VARCHAR; T == ?TYPE_VAR_STRING;
  497. T == ?TYPE_ENUM; T == ?TYPE_SET; T == ?TYPE_LONG_BLOB;
  498. T == ?TYPE_MEDIUM_BLOB; T == ?TYPE_BLOB; T == ?TYPE_TINY_BLOB;
  499. T == ?TYPE_GEOMETRY; T == ?TYPE_BIT; T == ?TYPE_DECIMAL;
  500. T == ?TYPE_NEWDECIMAL ->
  501. lenenc_str(Data);
  502. decode_binary(?TYPE_LONGLONG, <<Value:64/signed-little, Rest/binary>>) ->
  503. {Value, Rest};
  504. decode_binary(T, <<Value:32/signed-little, Rest/binary>>)
  505. when T == ?TYPE_LONG; T == ?TYPE_INT24 ->
  506. {Value, Rest};
  507. decode_binary(T, <<Value:16/signed-little, Rest/binary>>)
  508. when T == ?TYPE_SHORT; T == ?TYPE_YEAR ->
  509. {Value, Rest};
  510. decode_binary(?TYPE_TINY, <<Value:8, Rest/binary>>) ->
  511. {Value, Rest};
  512. decode_binary(?TYPE_DOUBLE, <<Value:64/float-little, Rest/binary>>) ->
  513. {Value, Rest};
  514. decode_binary(?TYPE_FLOAT, <<Value:32/float-little, Rest/binary>>) ->
  515. %% There is a precision loss when storing and fetching a 32-bit float.
  516. %% In the text protocol, it is obviously rounded. Storing 3.14 in a FLOAT
  517. %% column and fetching it using the text protocol, we get "3.14" which we
  518. %% parse to the Erlang double as close as possible to 3.14. Fetching the
  519. %% same value as a binary 32-bit float, we get 3.140000104904175. To achieve
  520. %% the same rounding after receiving it as a 32-bit float, we try to do the
  521. %% same rounding here as MySQL does when sending it over the text protocol.
  522. %%
  523. %% This comment explains the idea:
  524. %%
  525. %% Posted by Geoffrey Downs on March 10 2011 10:26am
  526. %%
  527. %% Following up... I *think* this is correct for the default float
  528. %% columns in mysql:
  529. %%
  530. %% var yourNumber = some floating point value
  531. %% max decimal precision = 10 ^ (-5 + floor(yourNumber log 10))
  532. %% So:
  533. %% 0 < x < 10 -> max precision is 0.00001
  534. %% 10 <= x < 100 -> max precision is 0.0001
  535. %% 100 <= x < 1000 -> max precision is 0.001
  536. %% etc.
  537. %%
  538. %% (From http://dev.mysql.com/doc/refman/5.7/en/problems-with-float.html
  539. %% fetched 10 Nov 2014)
  540. %%
  541. %% The above is almost correct, except for the example in the interval
  542. %% 0 < x < 1. There are 6 significant digits also for these numbers.
  543. %%
  544. %% Now, instead of P = 0.00001 we want the inverse 100000.0 but if we
  545. %% compute Factor = 1 / P we get a precision loss, so instead we do this:
  546. Factor = math:pow(10, floor(6 - math:log10(abs(Value)))),
  547. RoundedValue = round(Value * Factor) / Factor,
  548. {RoundedValue, Rest};
  549. decode_binary(?TYPE_DATE, <<Length, Data/binary>>) ->
  550. %% Coded in the same way as DATETIME and TIMESTAMP below, but returned in
  551. %% a simple triple.
  552. case {Length, Data} of
  553. {0, _} -> {{0, 0, 0}, Data};
  554. {4, <<Y:16/little, M, D, Rest/binary>>} -> {{Y, M, D}, Rest}
  555. end;
  556. decode_binary(T, <<Length, Data/binary>>)
  557. when T == ?TYPE_DATETIME; T == ?TYPE_TIMESTAMP ->
  558. %% length (1) -- number of bytes following (valid values: 0, 4, 7, 11)
  559. case {Length, Data} of
  560. {0, _} ->
  561. {{{0, 0, 0}, {0, 0, 0}}, Data};
  562. {4, <<Y:16/little, M, D, Rest/binary>>} ->
  563. {{{Y, M, D}, {0, 0, 0}}, Rest};
  564. {7, <<Y:16/little, M, D, H, Mi, S, Rest/binary>>} ->
  565. {{{Y, M, D}, {H, Mi, S}}, Rest};
  566. {11, <<Y:16/little, M, D, H, Mi, S, Micro:32/little, Rest/binary>>} ->
  567. {{{Y, M, D}, {H, Mi, S + 0.000001 * Micro}}, Rest}
  568. end;
  569. decode_binary(?TYPE_TIME, <<Length, Data/binary>>) ->
  570. %% length (1) -- number of bytes following (valid values: 0, 8, 12)
  571. %% is_negative (1) -- (1 if minus, 0 for plus)
  572. %% days (4) -- days
  573. %% hours (1) -- hours
  574. %% minutes (1) -- minutes
  575. %% seconds (1) -- seconds
  576. %% micro_seconds (4) -- micro-seconds
  577. case {Length, Data} of
  578. {0, _} ->
  579. {{0, {0, 0, 0}}, Data};
  580. {8, <<0, D:32/little, H, M, S, Rest/binary>>} ->
  581. {{D, {H, M, S}}, Rest};
  582. {12, <<0, D:32/little, H, M, S, Micro:32/little, Rest/binary>>} ->
  583. {{D, {H, M, S + 0.000001 * Micro}}, Rest};
  584. {8, <<1, D:32/little, H, M, S, Rest/binary>>} ->
  585. %% Negative time. Example: '-00:00:01' --> {-1,{23,59,59}}
  586. Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
  587. %Seconds = D * 86400 + calendar:time_to_seconds({H, M, S}),
  588. {calendar:seconds_to_daystime(-Seconds), Rest};
  589. {12, <<1, D:32/little, H, M, S, Micro:32/little, Rest/binary>>}
  590. when Micro > 0 ->
  591. %% Negate and convert to seconds, excl fractions
  592. Seconds = -(((D * 24 + H) * 60 + M) * 60 + S),
  593. %Seconds = -D * 86400 - calendar:time_to_seconds({H, M, S}),
  594. %% Subtract 1 second for the fractions
  595. {Days, {Hours, Minutes, Sec}} =
  596. calendar:seconds_to_daystime(Seconds - 1),
  597. %% Adding the fractions to Sec again makes it a float
  598. {{Days, {Hours, Minutes, Sec + 1 - 0.000001 * Micro}}, Rest}
  599. end.
  600. %% @doc Like trunc/1 but towards negative infinity instead of towards zero.
  601. floor(Value) ->
  602. Trunc = trunc(Value),
  603. if
  604. Trunc =< Value -> Trunc;
  605. Trunc > Value -> Trunc - 1 %% for negative values
  606. end.
  607. %% @doc Encodes a term reprenting av value as a binary for use in the binary
  608. %% protocol. As this is used to encode parameters for prepared statements, the
  609. %% encoding is in its required form, namely `<<Type:8, Sign:8, Value/binary>>'.
  610. %%
  611. %% TODO: Maybe change Erlang representation of BIT to `<<_:1>>'.
  612. -spec encode_param(term()) -> {TypeAndSign :: binary(), Data :: binary()}.
  613. encode_param(null) ->
  614. {<<?TYPE_NULL, 0>>, <<>>};
  615. encode_param(Value) when is_binary(Value) ->
  616. EncLength = lenenc_int_encode(byte_size(Value)),
  617. {<<?TYPE_VAR_STRING, 0>>, <<EncLength/binary, Value/binary>>};
  618. encode_param(Value) when is_integer(Value), Value >= 0 ->
  619. %% We send positive integers with the 'unsigned' flag set.
  620. if
  621. Value =< 16#ff ->
  622. {<<?TYPE_TINY, 16#80>>, <<Value:8>>};
  623. Value =< 16#ffff ->
  624. {<<?TYPE_SHORT, 16#80>>, <<Value:16/little>>};
  625. Value =< 16#ffffffff ->
  626. {<<?TYPE_LONG, 16#80>>, <<Value:32/little>>};
  627. Value =< 16#ffffffffffffffff ->
  628. {<<?TYPE_LONGLONG, 16#80>>, <<Value:64/little>>};
  629. true ->
  630. %% If larger than a 64-bit int we send it as a string. MySQL does
  631. %% silently cast strings in aithmetic expressions. Also, DECIMALs
  632. %% are always sent as strings.
  633. encode_param(integer_to_binary(Value))
  634. end;
  635. encode_param(Value) when is_integer(Value), Value < 0 ->
  636. if
  637. Value >= -16#80 ->
  638. {<<?TYPE_TINY, 0>>, <<Value:8>>};
  639. Value >= -16#8000 ->
  640. {<<?TYPE_SHORT, 0>>, <<Value:16/little>>};
  641. Value >= -16#80000000 ->
  642. {<<?TYPE_LONG, 0>>, <<Value:32/little>>};
  643. Value >= -16#8000000000000000 ->
  644. {<<?TYPE_LONGLONG, 0>>, <<Value:64/little>>};
  645. true ->
  646. encode_param(integer_to_binary(Value))
  647. end;
  648. encode_param(Value) when is_float(Value) ->
  649. {<<?TYPE_DOUBLE, 0>>, <<Value:64/float-little>>};
  650. encode_param({Y, M, D}) ->
  651. %% calendar:date()
  652. {<<?TYPE_DATE, 0>>, <<4, Y:16/little, M, D>>};
  653. encode_param({{Y, M, D}, {0, 0, 0}}) ->
  654. %% Datetime at midnight
  655. {<<?TYPE_DATETIME, 0>>, <<4, Y:16/little, M, D>>};
  656. encode_param({{Y, M, D}, {H, Mi, S}}) when is_integer(S) ->
  657. %% calendar:datetime()
  658. {<<?TYPE_DATETIME, 0>>, <<7, Y:16/little, M, D, H, Mi, S>>};
  659. encode_param({{Y, M, D}, {H, Mi, S}}) when is_float(S) ->
  660. %% calendar:datetime() with a float for seconds. This way it looks very
  661. %% similar to a datetime. Microseconds in MySQL timestamps are possible but
  662. %% not very common.
  663. Sec = trunc(S),
  664. Micro = round(1000000 * (S - Sec)),
  665. {<<?TYPE_DATETIME, 0>>, <<11, Y:16/little, M, D, H, Mi, Sec,
  666. Micro:32/little>>};
  667. encode_param({D, {H, M, S}}) when is_integer(S), D >= 0 ->
  668. %% calendar:seconds_to_daystime()
  669. {<<?TYPE_TIME, 0>>, <<8, 0, D:32/little, H, M, S>>};
  670. encode_param({D, {H, M, S}}) when is_integer(S), D < 0 ->
  671. %% Convert to seconds, negate and convert back to daystime form.
  672. %% Then set the minus flag.
  673. Seconds = ((D * 24 + H) * 60 + M) * 60 + S,
  674. {D1, {H1, M1, S1}} = calendar:seconds_to_daystime(-Seconds),
  675. {<<?TYPE_TIME, 0>>, <<8, 1, D1:32/little, H1, M1, S1>>};
  676. encode_param({D, {H, M, S}}) when is_float(S), D >= 0 ->
  677. S1 = trunc(S),
  678. Micro = round(1000000 * (S - S1)),
  679. {<<?TYPE_TIME, 0>>, <<12, 0, D:32/little, H, M, S1, Micro:32/little>>};
  680. encode_param({D, {H, M, S}}) when is_float(S), S > 0.0, D < 0 ->
  681. IntS = trunc(S),
  682. Micro = round(1000000 * (1 - S + IntS)),
  683. Seconds = (D * 24 + H) * 3600 + M * 60 + IntS + 1,
  684. {D1, {M1, H1, S1}} = calendar:seconds_to_daystime(-Seconds),
  685. {<<?TYPE_TIME, 0>>, <<12, 1, D1:32/little, H1, M1, S1, Micro:32/little>>};
  686. encode_param({D, {H, M, 0.0}}) ->
  687. encode_param({D, {H, M, 0}}).
  688. %% -- Protocol basics: packets --
  689. %% @doc Wraps Data in packet headers, sends it by calling SendFun and returns
  690. %% {ok, SeqNum1} where SeqNum1 is the next sequence number.
  691. -spec send_packet(sendfun(), Data :: binary(), SeqNum :: integer()) ->
  692. {ok, NextSeqNum :: integer()}.
  693. send_packet(SendFun, Data, SeqNum) ->
  694. {WithHeaders, SeqNum1} = add_packet_headers(Data, SeqNum),
  695. ok = SendFun(WithHeaders),
  696. {ok, SeqNum1}.
  697. %% @doc Receives data by calling RecvFun and removes the packet headers. Returns
  698. %% the packet contents and the next packet sequence number.
  699. -spec recv_packet(RecvFun :: recvfun(), SeqNum :: integer()) ->
  700. {ok, Data :: binary(), NextSeqNum :: integer()}.
  701. recv_packet(RecvFun, SeqNum) ->
  702. recv_packet(RecvFun, SeqNum, <<>>).
  703. %% @doc Receives data by calling RecvFun and removes packet headers. Returns the
  704. %% data and the next packet sequence number.
  705. -spec recv_packet(RecvFun :: recvfun(), ExpectSeqNum :: integer(),
  706. Acc :: binary()) ->
  707. {ok, Data :: binary(), NextSeqNum :: integer()}.
  708. recv_packet(RecvFun, ExpectSeqNum, Acc) ->
  709. {ok, Header} = RecvFun(4),
  710. {Size, ExpectSeqNum, More} = parse_packet_header(Header),
  711. {ok, Body} = RecvFun(Size),
  712. Acc1 = <<Acc/binary, Body/binary>>,
  713. NextSeqNum = (ExpectSeqNum + 1) band 16#ff,
  714. case More of
  715. false -> {ok, Acc1, NextSeqNum};
  716. true -> recv_packet(RecvFun, NextSeqNum, Acc1)
  717. end.
  718. %% @doc Parses a packet header (32 bits) and returns a tuple.
  719. %%
  720. %% The client should first read a header and parse it. Then read PacketLength
  721. %% bytes. If there are more packets, read another header and read a new packet
  722. %% length of payload until there are no more packets. The seq num should
  723. %% increment from 0 and may wrap around at 255 back to 0.
  724. %%
  725. %% When all packets are read and the payload of all packets are concatenated, it
  726. %% can be parsed using parse_response/1, etc. depending on what type of response
  727. %% is expected.
  728. -spec parse_packet_header(PackerHeader :: binary()) ->
  729. {PacketLength :: integer(),
  730. SeqNum :: integer(),
  731. MorePacketsExist :: boolean()}.
  732. parse_packet_header(<<PacketLength:24/little-integer, SeqNum:8/integer>>) ->
  733. {PacketLength, SeqNum, PacketLength == 16#ffffff}.
  734. %% @doc Splits a packet body into chunks and wraps them in headers. The
  735. %% resulting list is ready to sent to the socket.
  736. -spec add_packet_headers(PacketBody :: iodata(), SeqNum :: integer()) ->
  737. {PacketWithHeaders :: iodata(), NextSeqNum :: integer()}.
  738. add_packet_headers(PacketBody, SeqNum) ->
  739. Bin = iolist_to_binary(PacketBody),
  740. Size = size(Bin),
  741. SeqNum1 = (SeqNum + 1) rem 16#100,
  742. %% Todo: implement the case when Size >= 16#ffffff.
  743. if Size < 16#ffffff ->
  744. {[<<Size:24/little, SeqNum:8>>, Bin], SeqNum1}
  745. end.
  746. -spec parse_ok_packet(binary()) -> #ok{}.
  747. parse_ok_packet(<<?OK:8, Rest/binary>>) ->
  748. {AffectedRows, Rest1} = lenenc_int(Rest),
  749. {InsertId, Rest2} = lenenc_int(Rest1),
  750. <<StatusFlags:16/little, WarningCount:16/little, Msg/binary>> = Rest2,
  751. %% We have CLIENT_PROTOCOL_41 but not CLIENT_SESSION_TRACK enabled. The
  752. %% protocol is conditional. This is from the protocol documentation:
  753. %%
  754. %% if capabilities & CLIENT_PROTOCOL_41 {
  755. %% int<2> status_flags
  756. %% int<2> warning_count
  757. %% } elseif capabilities & CLIENT_TRANSACTIONS {
  758. %% int<2> status_flags
  759. %% }
  760. %% if capabilities & CLIENT_SESSION_TRACK {
  761. %% string<lenenc> info
  762. %% if status_flags & SERVER_SESSION_STATE_CHANGED {
  763. %% string<lenenc> session_state_changes
  764. %% }
  765. %% } else {
  766. %% string<EOF> info
  767. %% }
  768. #ok{affected_rows = AffectedRows,
  769. insert_id = InsertId,
  770. status = StatusFlags,
  771. warning_count = WarningCount,
  772. msg = Msg}.
  773. -spec parse_error_packet(binary()) -> #error{}.
  774. parse_error_packet(<<?ERROR:8, ErrNo:16/little, "#", SQLState:5/binary-unit:8,
  775. Msg/binary>>) ->
  776. %% Error, 4.1 protocol.
  777. %% (Older protocol: <<?ERROR:8, ErrNo:16/little, Msg/binary>>)
  778. #error{code = ErrNo, state = SQLState, msg = Msg}.
  779. -spec parse_eof_packet(binary()) -> #eof{}.
  780. parse_eof_packet(<<?EOF:8, NumWarnings:16/little, StatusFlags:16/little>>) ->
  781. %% EOF packet, 4.1 protocol.
  782. %% (Older protocol: <<?EOF:8>>)
  783. #eof{status = StatusFlags, warning_count = NumWarnings}.
  784. -spec hash_password(Password :: iodata(), Salt :: binary()) -> Hash :: binary().
  785. hash_password(Password, Salt) ->
  786. %% From the "MySQL Internals" manual:
  787. %% SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat>
  788. %% SHA1( SHA1( password ) ) )
  789. %% ----
  790. %% Make sure the salt is exactly 20 bytes.
  791. %%
  792. %% The auth data is obviously nul-terminated. For the "native" auth
  793. %% method, it should be a 20 byte salt, so let's trim it in this case.
  794. Salt1 = case Salt of
  795. <<SaltNoNul:20/binary-unit:8, 0>> -> SaltNoNul;
  796. _ when size(Salt) == 20 -> Salt
  797. end,
  798. %% Hash as described above.
  799. <<Hash1Num:160>> = Hash1 = crypto:hash(sha, Password),
  800. Hash2 = crypto:hash(sha, Hash1),
  801. <<Hash3Num:160>> = crypto:hash(sha, <<Salt1/binary, Hash2/binary>>),
  802. <<(Hash1Num bxor Hash3Num):160>>.
  803. %% --- Lowlevel: variable length integers and strings ---
  804. %% lenenc_int/1 decodes length-encoded-integer values
  805. -spec lenenc_int(Input :: binary()) -> {Value :: integer(), Rest :: binary()}.
  806. lenenc_int(<<Value:8, Rest/bits>>) when Value < 251 -> {Value, Rest};
  807. lenenc_int(<<16#fc:8, Value:16/little, Rest/binary>>) -> {Value, Rest};
  808. lenenc_int(<<16#fd:8, Value:24/little, Rest/binary>>) -> {Value, Rest};
  809. lenenc_int(<<16#fe:8, Value:64/little, Rest/binary>>) -> {Value, Rest}.
  810. %% Length-encoded-integer encode. Appends the encoded value to Acc.
  811. %% Values not representable in 64 bits are not accepted.
  812. -spec lenenc_int_encode(0..16#ffffffffffffffff) -> binary().
  813. lenenc_int_encode(Value) when Value < 0 ->
  814. error(badarg);
  815. lenenc_int_encode(Value) when Value < 251 ->
  816. <<Value>>;
  817. lenenc_int_encode(Value) when Value =< 16#ffff ->
  818. <<16#fc, Value:16/little>>;
  819. lenenc_int_encode(Value) when Value =< 16#ffffff ->
  820. <<16#fd, Value:24/little>>;
  821. lenenc_int_encode(Value) when Value =< 16#ffffffffffffffff ->
  822. <<16#fe, Value:64/little>>.
  823. %% lenenc_str/1 decodes length-encoded-string values
  824. -spec lenenc_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
  825. lenenc_str(Bin) ->
  826. {Length, Rest} = lenenc_int(Bin),
  827. <<String:Length/binary, Rest1/binary>> = Rest,
  828. {String, Rest1}.
  829. %% nts/1 decodes a nul-terminated string
  830. -spec nulterm_str(Input :: binary()) -> {String :: binary(), Rest :: binary()}.
  831. nulterm_str(Bin) ->
  832. [String, Rest] = binary:split(Bin, <<0>>),
  833. {String, Rest}.
  834. -ifdef(TEST).
  835. -include_lib("eunit/include/eunit.hrl").
  836. -include("server_status.hrl").
  837. %% Testing some of the internal functions, mostly the cases we don't cover in
  838. %% other tests.
  839. decode_text_test() ->
  840. %% Int types
  841. lists:foreach(fun (T) -> ?assertEqual(1, decode_text(T, <<"1">>)) end,
  842. [?TYPE_TINY, ?TYPE_SHORT, ?TYPE_LONG, ?TYPE_LONGLONG,
  843. ?TYPE_INT24, ?TYPE_YEAR, ?TYPE_BIT]),
  844. %% Floating point and decimal numbers
  845. lists:foreach(fun (T) -> ?assertEqual(3.0, decode_text(T, <<"3.0">>)) end,
  846. [?TYPE_FLOAT, ?TYPE_DOUBLE]),
  847. %% Decimal types
  848. lists:foreach(fun (T) ->
  849. ?assertEqual(<<"3.0">>, decode_text(T, <<"3.0">>))
  850. end,
  851. [?TYPE_DECIMAL, ?TYPE_NEWDECIMAL]),
  852. ?assertEqual(3.0, decode_text(?TYPE_FLOAT, <<"3">>)),
  853. ?assertEqual(30.0, decode_text(?TYPE_FLOAT, <<"3e1">>)),
  854. ?assertEqual(3, decode_text(?TYPE_LONG, <<"3">>)),
  855. %% Date and time
  856. ?assertEqual({2014, 11, 01}, decode_text(?TYPE_DATE, <<"2014-11-01">>)),
  857. ?assertEqual({0, {23, 59, 01}}, decode_text(?TYPE_TIME, <<"23:59:01">>)),
  858. ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
  859. decode_text(?TYPE_DATETIME, <<"2014-11-01 23:59:01">>)),
  860. ?assertEqual({{2014, 11, 01}, {23, 59, 01}},
  861. decode_text(?TYPE_TIMESTAMP, <<"2014-11-01 23:59:01">>)),
  862. %% Strings and blobs
  863. lists:foreach(fun (T) ->
  864. ?assertEqual(<<"x">>, decode_text(T, <<"x">>))
  865. end,
  866. [?TYPE_VARCHAR, ?TYPE_ENUM, ?TYPE_TINY_BLOB,
  867. ?TYPE_MEDIUM_BLOB, ?TYPE_LONG_BLOB, ?TYPE_BLOB,
  868. ?TYPE_VAR_STRING, ?TYPE_STRING, ?TYPE_GEOMETRY]),
  869. %% Set
  870. ?assertEqual(sets:from_list([<<"b">>, <<"a">>]),
  871. decode_text(?TYPE_SET, <<"a,b">>)),
  872. ?assertEqual(sets:from_list([]), decode_text(?TYPE_SET, <<>>)),
  873. %% NULL
  874. ?assertEqual(null, decode_text(?TYPE_FLOAT, null)),
  875. ok.
  876. decode_binary_test() ->
  877. %% Test the special rounding we apply to (single precision) floats.
  878. %?assertEqual({1.0, <<>>},
  879. % decode_binary(?TYPE_FLOAT, <<1.0:32/float-little>>)),
  880. %?assertEqual({0.2, <<>>},
  881. % decode_binary(?TYPE_FLOAT, <<0.2:32/float-little>>)),
  882. %?assertEqual({-33.3333, <<>>},
  883. % decode_binary(?TYPE_FLOAT, <<-33.333333:32/float-little>>)),
  884. %?assertEqual({0.000123457, <<>>},
  885. % decode_binary(?TYPE_FLOAT, <<0.00012345678:32/float-little>>)),
  886. %?assertEqual({1234.57, <<>>},
  887. % decode_binary(?TYPE_FLOAT, <<1234.56789:32/float-little>>)),
  888. ok.
  889. null_bitmap_test() ->
  890. ?assertEqual({<<0, 1:1>>, <<>>}, null_bitmap_decode(9, <<0, 4>>, 2)),
  891. ?assertEqual(<<0, 4>>, null_bitmap_encode(<<0, 1:1>>, 2)),
  892. ok.
  893. lenenc_int_test() ->
  894. %% decode
  895. ?assertEqual({40, <<>>}, lenenc_int(<<40>>)),
  896. ?assertEqual({16#ff, <<>>}, lenenc_int(<<16#fc, 255, 0>>)),
  897. ?assertEqual({16#33aaff, <<>>}, lenenc_int(<<16#fd, 16#ff, 16#aa, 16#33>>)),
  898. ?assertEqual({16#12345678, <<>>}, lenenc_int(<<16#fe, 16#78, 16#56, 16#34,
  899. 16#12, 0, 0, 0, 0>>)),
  900. %% encode
  901. ?assertEqual(<<40>>, lenenc_int_encode(40)),
  902. ?assertEqual(<<16#fc, 255, 0>>, lenenc_int_encode(255)),
  903. ?assertEqual(<<16#fd, 16#ff, 16#aa, 16#33>>,
  904. lenenc_int_encode(16#33aaff)),
  905. ?assertEqual(<<16#fe, 16#78, 16#56, 16#34, 16#12, 0, 0, 0, 0>>,
  906. lenenc_int_encode(16#12345678)),
  907. ok.
  908. lenenc_str_test() ->
  909. ?assertEqual({<<"Foo">>, <<"bar">>}, lenenc_str(<<3, "Foobar">>)).
  910. nulterm_test() ->
  911. ?assertEqual({<<"Foo">>, <<"bar">>}, nulterm_str(<<"Foo", 0, "bar">>)).
  912. parse_header_test() ->
  913. %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
  914. Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
  915. <<Header:4/binary-unit:8, Body/binary>> = Packet,
  916. %% Check header contents and body length
  917. ?assertEqual({size(Body), 5, false}, parse_packet_header(Header)),
  918. ok.
  919. add_packet_headers_test() ->
  920. {Data, 43} = add_packet_headers(<<"foo">>, 42),
  921. ?assertEqual(<<3, 0, 0, 42, "foo">>, list_to_binary(Data)).
  922. parse_ok_test() ->
  923. Body = <<0, 5, 1, 2, 0, 0, 0, "Foo">>,
  924. ?assertEqual(#ok{affected_rows = 5,
  925. insert_id = 1,
  926. status = ?SERVER_STATUS_AUTOCOMMIT,
  927. warning_count = 0,
  928. msg = <<"Foo">>},
  929. parse_ok_packet(Body)).
  930. parse_error_test() ->
  931. %% Protocol 4.1
  932. Body = <<255, 42, 0, "#", "XYZxx", "Foo">>,
  933. ?assertEqual(#error{code = 42, state = <<"XYZxx">>, msg = <<"Foo">>},
  934. parse_error_packet(Body)),
  935. ok.
  936. parse_eof_test() ->
  937. %% Example from "MySQL Internals", revision 307, section 14.1.3.3 EOF_Packet
  938. Packet = <<16#05, 16#00, 16#00, 16#05, 16#fe, 16#00, 16#00, 16#02, 16#00>>,
  939. <<_Header:4/binary-unit:8, Body/binary>> = Packet,
  940. %% Ignore header. Parse body as an eof_packet.
  941. ?assertEqual(#eof{warning_count = 0,
  942. status = ?SERVER_STATUS_AUTOCOMMIT},
  943. parse_eof_packet(Body)),
  944. ok.
  945. hash_password_test() ->
  946. ?assertEqual(<<222,207,222,139,41,181,202,13,191,241,
  947. 234,234,73,127,244,101,205,3,28,251>>,
  948. hash_password(<<"foo">>, <<"abcdefghijklmnopqrst">>)).
  949. -endif.