protocol.ipp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092
  1. //
  2. // Copyright (c) 2019-2023 Ruben Perez Hidalgo (rubenperez038 at gmail dot com)
  3. //
  4. // Distributed under the Boost Software License, Version 1.0. (See accompanying
  5. // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  6. //
  7. #ifndef BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_IPP
  8. #define BHO_MYSQL_IMPL_INTERNAL_PROTOCOL_PROTOCOL_IPP
  9. #pragma once
  10. #include <asio2/bho/mysql/client_errc.hpp>
  11. #include <asio2/bho/mysql/common_server_errc.hpp>
  12. #include <asio2/bho/mysql/error_categories.hpp>
  13. #include <asio2/bho/mysql/error_code.hpp>
  14. #include <asio2/bho/mysql/field_kind.hpp>
  15. #include <asio2/bho/mysql/string_view.hpp>
  16. #include <asio2/bho/mysql/detail/config.hpp>
  17. #include <asio2/bho/mysql/impl/internal/error/server_error_to_string.hpp>
  18. #include <asio2/bho/mysql/impl/internal/make_string_view.hpp>
  19. #include <asio2/bho/mysql/impl/internal/protocol/basic_types.hpp>
  20. #include <asio2/bho/mysql/impl/internal/protocol/binary_serialization.hpp>
  21. #include <asio2/bho/mysql/impl/internal/protocol/capabilities.hpp>
  22. #include <asio2/bho/mysql/impl/internal/protocol/constants.hpp>
  23. #include <asio2/bho/mysql/impl/internal/protocol/deserialize_binary_field.hpp>
  24. #include <asio2/bho/mysql/impl/internal/protocol/deserialize_text_field.hpp>
  25. #include <asio2/bho/mysql/impl/internal/protocol/null_bitmap_traits.hpp>
  26. #include <asio2/bho/mysql/impl/internal/protocol/protocol.hpp>
  27. #include <asio2/bho/mysql/impl/internal/protocol/serialization.hpp>
  28. #include <asio2/bho/core/ignore_unused.hpp>
  29. #include <asio2/bho/core/span.hpp>
  30. #include <cstddef>
  31. namespace bho {
  32. namespace mysql {
  33. namespace detail {
  34. // Constants
  35. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t handshake_protocol_version_9 = 9;
  36. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t handshake_protocol_version_10 = 10;
  37. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t error_packet_header = 0xff;
  38. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t ok_packet_header = 0x00;
  39. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t eof_packet_header = 0xfe;
  40. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t auth_switch_request_header = 0xfe;
  41. BHO_MYSQL_STATIC_IF_COMPILED constexpr std::uint8_t auth_more_data_header = 0x01;
  42. BHO_MYSQL_STATIC_IF_COMPILED constexpr string_view fast_auth_complete_challenge = make_string_view("\3");
  43. // Helpers
  44. BHO_MYSQL_STATIC_OR_INLINE
  45. void serialize_command_id(span<std::uint8_t> buff, std::uint8_t command_id) noexcept
  46. {
  47. BHO_ASSERT(buff.size() >= 1u);
  48. buff[0] = command_id;
  49. }
  50. struct frame_header_packet
  51. {
  52. int3 packet_size;
  53. std::uint8_t sequence_number;
  54. };
  55. // Maps from an actual value to a protocol_field_type (for execute statement). Only value's type is used
  56. static protocol_field_type get_protocol_field_type(field_view input) noexcept
  57. {
  58. switch (input.kind())
  59. {
  60. case field_kind::null: return protocol_field_type::null;
  61. case field_kind::int64: return protocol_field_type::longlong;
  62. case field_kind::uint64: return protocol_field_type::longlong;
  63. case field_kind::string: return protocol_field_type::string;
  64. case field_kind::blob: return protocol_field_type::blob;
  65. case field_kind::float_: return protocol_field_type::float_;
  66. case field_kind::double_: return protocol_field_type::double_;
  67. case field_kind::date: return protocol_field_type::date;
  68. case field_kind::datetime: return protocol_field_type::datetime;
  69. case field_kind::time: return protocol_field_type::time;
  70. default: BHO_ASSERT(false); return protocol_field_type::null;
  71. }
  72. }
  73. } // namespace detail
  74. } // namespace mysql
  75. } // namespace bho
  76. // Frame header
  77. void bho::mysql::detail::serialize_frame_header(
  78. frame_header msg,
  79. span<std::uint8_t, frame_header_size> buffer
  80. ) noexcept
  81. {
  82. BHO_ASSERT(msg.size <= 0xffffff); // range check
  83. serialization_context ctx(buffer.data());
  84. frame_header_packet pack{int3{msg.size}, msg.sequence_number};
  85. serialize(ctx, pack.packet_size, pack.sequence_number);
  86. }
  87. bho::mysql::detail::frame_header bho::mysql::detail::deserialize_frame_header(
  88. span<const std::uint8_t, frame_header_size> buffer
  89. ) noexcept
  90. {
  91. frame_header_packet pack{};
  92. deserialization_context ctx(buffer.data(), buffer.size());
  93. auto err = deserialize(ctx, pack.packet_size, pack.sequence_number);
  94. BHO_ASSERT(err == deserialize_errc::ok);
  95. bho::ignore_unused(err);
  96. return frame_header{pack.packet_size.value, pack.sequence_number};
  97. }
  98. // OK packets
  99. bho::mysql::error_code bho::mysql::detail::deserialize_ok_packet(
  100. span<const std::uint8_t> msg,
  101. ok_view& output
  102. ) noexcept
  103. {
  104. struct ok_packet
  105. {
  106. // header: int<1> header 0x00 or 0xFE the OK packet header
  107. int_lenenc affected_rows;
  108. int_lenenc last_insert_id;
  109. std::uint16_t status_flags; // server_status_flags
  110. std::uint16_t warnings;
  111. // CLIENT_SESSION_TRACK: not implemented
  112. string_lenenc info;
  113. } pack{};
  114. deserialization_context ctx(msg);
  115. auto err = deserialize(ctx, pack.affected_rows, pack.last_insert_id, pack.status_flags, pack.warnings);
  116. if (err != deserialize_errc::ok)
  117. return to_error_code(err);
  118. if (ctx.enough_size(1)) // message is optional, may be omitted
  119. {
  120. err = deserialize(ctx, pack.info);
  121. if (err != deserialize_errc::ok)
  122. return to_error_code(err);
  123. }
  124. output = {
  125. pack.affected_rows.value,
  126. pack.last_insert_id.value,
  127. pack.status_flags,
  128. pack.warnings,
  129. pack.info.value,
  130. };
  131. return ctx.check_extra_bytes();
  132. }
  133. // Error packets
  134. bho::mysql::error_code bho::mysql::detail::deserialize_error_packet(
  135. span<const std::uint8_t> msg,
  136. err_view& output
  137. ) noexcept
  138. {
  139. struct err_packet
  140. {
  141. // int<1> header 0xFF ERR packet header
  142. std::uint16_t error_code;
  143. string_fixed<1> sql_state_marker;
  144. string_fixed<5> sql_state;
  145. string_eof error_message;
  146. } pack{};
  147. deserialization_context ctx(msg);
  148. auto err = deserialize(ctx, pack.error_code, pack.sql_state_marker, pack.sql_state, pack.error_message);
  149. if (err != deserialize_errc::ok)
  150. return to_error_code(err);
  151. output = err_view{
  152. pack.error_code,
  153. pack.error_message.value,
  154. };
  155. return ctx.check_extra_bytes();
  156. }
  157. bho::mysql::error_code bho::mysql::detail::process_error_packet(
  158. span<const std::uint8_t> msg,
  159. db_flavor flavor,
  160. diagnostics& diag
  161. )
  162. {
  163. err_view error_packet{};
  164. auto err = deserialize_error_packet(msg, error_packet);
  165. if (err)
  166. return err;
  167. // Error message
  168. access::get_impl(diag).assign_server(error_packet.error_message);
  169. // Error code
  170. if (common_error_to_string(error_packet.error_code))
  171. {
  172. // This is an error shared between MySQL and MariaDB, represented as a common_server_errc.
  173. // get_common_error_message will check that the code has a common_server_errc representation
  174. // (the common error range has "holes" because of removed error codes)
  175. return static_cast<common_server_errc>(error_packet.error_code);
  176. }
  177. else
  178. {
  179. // This is a MySQL or MariaDB specific code. There is no fixed list of error codes,
  180. // as they both keep adding more codes, so no validation happens.
  181. const auto& cat = flavor == db_flavor::mysql ? get_mysql_server_category()
  182. : get_mariadb_server_category();
  183. return error_code(error_packet.error_code, cat);
  184. }
  185. }
  186. // Column definition
  187. bho::mysql::error_code bho::mysql::detail::deserialize_column_definition(
  188. span<const std::uint8_t> input,
  189. coldef_view& output
  190. ) noexcept
  191. {
  192. deserialization_context ctx(input);
  193. struct column_definition_packet
  194. {
  195. string_lenenc catalog; // always "def"
  196. string_lenenc schema; // database
  197. string_lenenc table; // virtual table
  198. string_lenenc org_table; // physical table
  199. string_lenenc name; // virtual column name
  200. string_lenenc org_name; // physical column name
  201. string_lenenc fixed_fields;
  202. } pack{};
  203. // pack.fixed_fields itself is a structure like this.
  204. // The proto allows for extensibility here - adding fields just increasing fixed_fields.length
  205. struct fixed_fields_pack
  206. {
  207. std::uint16_t character_set; // collation id, somehow named character_set in the protocol docs
  208. std::uint32_t column_length; // maximum length of the field
  209. protocol_field_type type; // type of the column as defined in enum_field_types
  210. std::uint16_t flags; // Flags as defined in Column Definition Flags
  211. std::uint8_t decimals; // max shown decimal digits. 0x00 for int/static strings; 0x1f for
  212. // dynamic strings, double, float
  213. } fixed_fields{};
  214. // Deserialize the main structure
  215. auto err = deserialize(
  216. ctx,
  217. pack.catalog,
  218. pack.schema,
  219. pack.table,
  220. pack.org_table,
  221. pack.name,
  222. pack.org_name,
  223. pack.fixed_fields
  224. );
  225. if (err != deserialize_errc::ok)
  226. return to_error_code(err);
  227. // Deserialize the fixed_fields structure.
  228. // Intentionally not checking for extra bytes here, since there may be unknown fields that should just get
  229. // ignored
  230. deserialization_context subctx(
  231. reinterpret_cast<const std::uint8_t*>(pack.fixed_fields.value.data()),
  232. pack.fixed_fields.value.size()
  233. );
  234. err = deserialize(
  235. subctx,
  236. fixed_fields.character_set,
  237. fixed_fields.column_length,
  238. fixed_fields.type,
  239. fixed_fields.flags,
  240. fixed_fields.decimals
  241. );
  242. if (err != deserialize_errc::ok)
  243. return to_error_code(err);
  244. // Compose output
  245. output = coldef_view{
  246. pack.schema.value,
  247. pack.table.value,
  248. pack.org_table.value,
  249. pack.name.value,
  250. pack.org_name.value,
  251. fixed_fields.character_set,
  252. fixed_fields.column_length,
  253. compute_column_type(fixed_fields.type, fixed_fields.flags, fixed_fields.character_set),
  254. fixed_fields.flags,
  255. fixed_fields.decimals,
  256. };
  257. return ctx.check_extra_bytes();
  258. }
  259. // quit
  260. std::size_t bho::mysql::detail::quit_command::get_size() const noexcept { return 1u; }
  261. void bho::mysql::detail::quit_command::serialize(span<std::uint8_t> buff) const noexcept
  262. {
  263. serialize_command_id(buff, 0x01);
  264. }
  265. // ping
  266. std::size_t bho::mysql::detail::ping_command::get_size() const noexcept { return 1u; }
  267. void bho::mysql::detail::ping_command::serialize(span<std::uint8_t> buff) const noexcept
  268. {
  269. serialize_command_id(buff, 0x0e);
  270. }
  271. // reset connection
  272. std::size_t bho::mysql::detail::reset_connection_command::get_size() const noexcept { return 1u; }
  273. void bho::mysql::detail::reset_connection_command::serialize(span<std::uint8_t> buff) const noexcept
  274. {
  275. serialize_command_id(buff, 0x1f);
  276. }
  277. bho::mysql::error_code bho::mysql::detail::deserialize_ok_response(
  278. span<const std::uint8_t> message,
  279. db_flavor flavor,
  280. diagnostics& diag
  281. )
  282. {
  283. // Header
  284. std::uint8_t header{};
  285. deserialization_context ctx(message);
  286. auto err = to_error_code(deserialize(ctx, header));
  287. if (err)
  288. return err;
  289. if (header == ok_packet_header)
  290. {
  291. // Verify that the ok_packet is correct
  292. ok_view ok{};
  293. return deserialize_ok_packet(ctx.to_span(), ok);
  294. }
  295. else if (header == error_packet_header)
  296. {
  297. // Theoretically, the server can answer with an error packet, too
  298. return process_error_packet(ctx.to_span(), flavor, diag);
  299. }
  300. else
  301. {
  302. // Invalid message
  303. return client_errc::protocol_value_error;
  304. }
  305. }
  306. // query
  307. std::size_t bho::mysql::detail::query_command::get_size() const noexcept
  308. {
  309. return ::bho::mysql::detail::get_size(string_eof{query}) + 1; // command ID
  310. }
  311. void bho::mysql::detail::query_command::serialize(span<std::uint8_t> buff) const noexcept
  312. {
  313. constexpr std::uint8_t command_id = 0x03;
  314. BHO_ASSERT(buff.size() >= get_size());
  315. serialization_context ctx(buff.data());
  316. ::bho::mysql::detail::serialize(ctx, command_id, string_eof{query});
  317. }
  318. // prepare statement
  319. std::size_t bho::mysql::detail::prepare_stmt_command::get_size() const noexcept
  320. {
  321. return ::bho::mysql::detail::get_size(string_eof{stmt}) + 1; // command ID
  322. }
  323. void bho::mysql::detail::prepare_stmt_command::serialize(span<std::uint8_t> buff) const noexcept
  324. {
  325. constexpr std::uint8_t command_id = 0x16;
  326. BHO_ASSERT(buff.size() >= get_size());
  327. serialization_context ctx(buff.data());
  328. ::bho::mysql::detail::serialize(ctx, command_id, string_eof{stmt});
  329. }
  330. bho::mysql::error_code bho::mysql::detail::deserialize_prepare_stmt_response_impl(
  331. span<const std::uint8_t> message,
  332. prepare_stmt_response& output
  333. ) noexcept
  334. {
  335. struct com_stmt_prepare_ok_packet
  336. {
  337. // std::uint8_t status: must be 0
  338. std::uint32_t statement_id;
  339. std::uint16_t num_columns;
  340. std::uint16_t num_params;
  341. std::uint8_t reserved_1; // must be 0
  342. std::uint16_t warning_count;
  343. // std::uint8_t metadata_follows when CLIENT_OPTIONAL_RESULTSET_METADATA: not implemented
  344. } pack{};
  345. deserialization_context ctx(message);
  346. auto err = deserialize(
  347. ctx,
  348. pack.statement_id,
  349. pack.num_columns,
  350. pack.num_params,
  351. pack.reserved_1,
  352. pack.warning_count
  353. );
  354. if (err != deserialize_errc::ok)
  355. return to_error_code(err);
  356. output = prepare_stmt_response{
  357. pack.statement_id,
  358. pack.num_columns,
  359. pack.num_params,
  360. };
  361. return ctx.check_extra_bytes();
  362. }
  363. bho::mysql::error_code bho::mysql::detail::deserialize_prepare_stmt_response(
  364. span<const std::uint8_t> message,
  365. db_flavor flavor,
  366. prepare_stmt_response& output,
  367. diagnostics& diag
  368. )
  369. {
  370. deserialization_context ctx(message);
  371. std::uint8_t msg_type = 0;
  372. auto err = to_error_code(deserialize(ctx, msg_type));
  373. if (err)
  374. return err;
  375. if (msg_type == error_packet_header)
  376. {
  377. return process_error_packet(ctx.to_span(), flavor, diag);
  378. }
  379. else if (msg_type != 0)
  380. {
  381. return client_errc::protocol_value_error;
  382. }
  383. else
  384. {
  385. return deserialize_prepare_stmt_response_impl(ctx.to_span(), output);
  386. }
  387. }
  388. // execute statement
  389. // The wire layout is as follows:
  390. // command ID
  391. // std::uint32_t statement_id;
  392. // std::uint8_t flags;
  393. // std::uint32_t iteration_count;
  394. // if num_params > 0:
  395. // NULL bitmap
  396. // std::uint8_t new_params_bind_flag;
  397. // array<meta_packet, num_params> meta;
  398. // protocol_field_type type;
  399. // std::uint8_t unsigned_flag;
  400. // array<field_view, num_params> params;
  401. std::size_t bho::mysql::detail::execute_stmt_command::get_size() const noexcept
  402. {
  403. constexpr std::size_t param_meta_packet_size = 2; // type + unsigned flag
  404. constexpr std::size_t stmt_execute_packet_head_size = 1 // command ID
  405. + 4 // statement_id
  406. + 1 // flags
  407. + 4; // iteration_count
  408. std::size_t res = stmt_execute_packet_head_size;
  409. auto num_params = params.size();
  410. if (num_params > 0u)
  411. {
  412. res += null_bitmap_traits(stmt_execute_null_bitmap_offset, num_params).byte_count();
  413. res += 1; // new_params_bind_flag
  414. res += param_meta_packet_size * num_params;
  415. for (field_view param : params)
  416. {
  417. res += ::bho::mysql::detail::get_size(param);
  418. }
  419. }
  420. return res;
  421. }
  422. void bho::mysql::detail::execute_stmt_command::serialize(span<std::uint8_t> buff) const noexcept
  423. {
  424. constexpr std::uint8_t command_id = 0x17;
  425. serialization_context ctx(buff.data());
  426. BHO_ASSERT(buff.size() >= get_size());
  427. std::uint32_t statement_id = this->statement_id;
  428. std::uint8_t flags = 0;
  429. std::uint32_t iteration_count = 1;
  430. std::uint8_t new_params_bind_flag = 1;
  431. ::bho::mysql::detail::serialize(ctx, command_id, statement_id, flags, iteration_count);
  432. // Number of parameters
  433. auto num_params = params.size();
  434. if (num_params > 0)
  435. {
  436. // NULL bitmap
  437. null_bitmap_traits traits(stmt_execute_null_bitmap_offset, num_params);
  438. std::memset(ctx.first(), 0, traits.byte_count()); // Initialize to zeroes
  439. for (std::size_t i = 0; i < num_params; ++i)
  440. {
  441. if (params[i].is_null())
  442. {
  443. traits.set_null(ctx.first(), i);
  444. }
  445. }
  446. ctx.advance(traits.byte_count());
  447. // new parameters bind flag
  448. ::bho::mysql::detail::serialize(ctx, new_params_bind_flag);
  449. // value metadata
  450. for (field_view param : params)
  451. {
  452. protocol_field_type type = get_protocol_field_type(param);
  453. std::uint8_t unsigned_flag = param.is_uint64() ? std::uint8_t(0x80) : std::uint8_t(0);
  454. ::bho::mysql::detail::serialize(ctx, type, unsigned_flag);
  455. }
  456. // actual values
  457. for (field_view param : params)
  458. {
  459. ::bho::mysql::detail::serialize(ctx, param);
  460. }
  461. }
  462. }
  463. // close statement
  464. std::size_t bho::mysql::detail::close_stmt_command::get_size() const noexcept { return 5u; }
  465. void bho::mysql::detail::close_stmt_command::serialize(span<std::uint8_t> buff) const noexcept
  466. {
  467. constexpr std::uint8_t command_id = 0x19;
  468. serialization_context ctx(buff.data());
  469. BHO_ASSERT(buff.size() >= get_size());
  470. ::bho::mysql::detail::serialize(ctx, command_id, statement_id);
  471. }
  472. // execute response
  473. bho::mysql::detail::execute_response bho::mysql::detail::deserialize_execute_response(
  474. span<const std::uint8_t> msg,
  475. db_flavor flavor,
  476. diagnostics& diag
  477. ) noexcept
  478. {
  479. // Response may be: ok_packet, err_packet, local infile request (not implemented)
  480. // If it is none of this, then the message type itself is the beginning of
  481. // a length-encoded int containing the field count
  482. deserialization_context ctx(msg);
  483. std::uint8_t msg_type = 0;
  484. auto err = to_error_code(deserialize(ctx, msg_type));
  485. if (err)
  486. return err;
  487. if (msg_type == ok_packet_header)
  488. {
  489. ok_view ok{};
  490. err = deserialize_ok_packet(ctx.to_span(), ok);
  491. if (err)
  492. return err;
  493. return ok;
  494. }
  495. else if (msg_type == error_packet_header)
  496. {
  497. return process_error_packet(ctx.to_span(), flavor, diag);
  498. }
  499. else
  500. {
  501. // Resultset with metadata. First packet is an int_lenenc with
  502. // the number of field definitions to expect. Message type is part
  503. // of this packet, so we must rewind the context
  504. ctx.rewind(1);
  505. int_lenenc num_fields;
  506. err = to_error_code(deserialize(ctx, num_fields));
  507. if (err)
  508. return err;
  509. err = ctx.check_extra_bytes();
  510. if (err)
  511. return err;
  512. // We should have at least one field.
  513. // The max number of fields is some value around 1024. For simplicity/extensibility,
  514. // we accept anything less than 0xffff
  515. if (num_fields.value == 0 || num_fields.value > 0xffffu)
  516. {
  517. return make_error_code(client_errc::protocol_value_error);
  518. }
  519. return static_cast<std::size_t>(num_fields.value);
  520. }
  521. }
  522. bho::mysql::detail::row_message bho::mysql::detail::deserialize_row_message(
  523. span<const std::uint8_t> msg,
  524. db_flavor flavor,
  525. diagnostics& diag
  526. )
  527. {
  528. // Message type: row, error or eof?
  529. std::uint8_t msg_type = 0;
  530. deserialization_context ctx(msg);
  531. auto deser_errc = deserialize(ctx, msg_type);
  532. if (deser_errc != deserialize_errc::ok)
  533. {
  534. return to_error_code(deser_errc);
  535. }
  536. if (msg_type == eof_packet_header)
  537. {
  538. // end of resultset => this is a ok_packet, not a row
  539. ok_view ok{};
  540. auto err = deserialize_ok_packet(ctx.to_span(), ok);
  541. if (err)
  542. return err;
  543. return ok;
  544. }
  545. else if (msg_type == error_packet_header)
  546. {
  547. // An error occurred during the generation of the rows
  548. return process_error_packet(ctx.to_span(), flavor, diag);
  549. }
  550. else
  551. {
  552. // An actual row
  553. ctx.rewind(1); // keep the 'message type' byte, as it is part of the actual message
  554. return span<const std::uint8_t>(ctx.first(), ctx.size());
  555. }
  556. }
  557. // Deserialize row
  558. namespace bho {
  559. namespace mysql {
  560. namespace detail {
  561. BHO_MYSQL_STATIC_OR_INLINE
  562. bool is_next_field_null(const deserialization_context& ctx) noexcept
  563. {
  564. if (!ctx.enough_size(1))
  565. return false;
  566. return *ctx.first() == 0xfb;
  567. }
  568. BHO_MYSQL_STATIC_OR_INLINE
  569. error_code deserialize_text_row(
  570. deserialization_context& ctx,
  571. metadata_collection_view meta,
  572. field_view* output
  573. )
  574. {
  575. for (std::vector<field_view>::size_type i = 0; i < meta.size(); ++i)
  576. {
  577. if (is_next_field_null(ctx))
  578. {
  579. ctx.advance(1);
  580. output[i] = field_view(nullptr);
  581. }
  582. else
  583. {
  584. string_lenenc value_str;
  585. auto err = deserialize(ctx, value_str);
  586. if (err != deserialize_errc::ok)
  587. return to_error_code(err);
  588. err = deserialize_text_field(value_str.value, meta[i], output[i]);
  589. if (err != deserialize_errc::ok)
  590. return to_error_code(err);
  591. }
  592. }
  593. if (!ctx.empty())
  594. return client_errc::extra_bytes;
  595. return error_code();
  596. }
  597. BHO_MYSQL_STATIC_OR_INLINE
  598. error_code deserialize_binary_row(
  599. deserialization_context& ctx,
  600. metadata_collection_view meta,
  601. field_view* output
  602. )
  603. {
  604. // Skip packet header (it is not part of the message in the binary
  605. // protocol but it is in the text protocol, so we include it for homogeneity)
  606. if (!ctx.enough_size(1))
  607. return client_errc::incomplete_message;
  608. ctx.advance(1);
  609. // Number of fields
  610. std::size_t num_fields = meta.size();
  611. // Null bitmap
  612. null_bitmap_traits null_bitmap(binary_row_null_bitmap_offset, num_fields);
  613. const std::uint8_t* null_bitmap_begin = ctx.first();
  614. if (!ctx.enough_size(null_bitmap.byte_count()))
  615. return client_errc::incomplete_message;
  616. ctx.advance(null_bitmap.byte_count());
  617. // Actual values
  618. for (std::vector<field_view>::size_type i = 0; i < num_fields; ++i)
  619. {
  620. if (null_bitmap.is_null(null_bitmap_begin, i))
  621. {
  622. output[i] = field_view(nullptr);
  623. }
  624. else
  625. {
  626. auto err = deserialize_binary_field(ctx, meta[i], output[i]);
  627. if (err != deserialize_errc::ok)
  628. return to_error_code(err);
  629. }
  630. }
  631. // Check for remaining bytes
  632. if (!ctx.empty())
  633. return make_error_code(client_errc::extra_bytes);
  634. return error_code();
  635. }
  636. } // namespace detail
  637. } // namespace mysql
  638. } // namespace bho
  639. bho::mysql::error_code bho::mysql::detail::deserialize_row(
  640. resultset_encoding encoding,
  641. span<const std::uint8_t> buff,
  642. metadata_collection_view meta,
  643. span<field_view> output
  644. )
  645. {
  646. BHO_ASSERT(meta.size() == output.size());
  647. deserialization_context ctx(buff);
  648. return encoding == detail::resultset_encoding::text ? deserialize_text_row(ctx, meta, output.data())
  649. : deserialize_binary_row(ctx, meta, output.data());
  650. }
  651. // Server hello
  652. namespace bho {
  653. namespace mysql {
  654. namespace detail {
  655. BHO_MYSQL_STATIC_OR_INLINE
  656. capabilities compose_capabilities(string_fixed<2> low, string_fixed<2> high) noexcept
  657. {
  658. std::uint32_t res = 0;
  659. auto capabilities_begin = reinterpret_cast<std::uint8_t*>(&res);
  660. memcpy(capabilities_begin, low.value.data(), 2);
  661. memcpy(capabilities_begin + 2, high.value.data(), 2);
  662. return capabilities(bho::endian::little_to_native(res));
  663. }
  664. BHO_MYSQL_STATIC_OR_INLINE
  665. db_flavor parse_db_version(string_view version_string) noexcept
  666. {
  667. return version_string.find("MariaDB") != string_view::npos ? db_flavor::mariadb : db_flavor::mysql;
  668. }
  669. BHO_MYSQL_STATIC_IF_COMPILED
  670. constexpr std::uint8_t server_hello_auth1_length = 8;
  671. } // namespace detail
  672. } // namespace mysql
  673. } // namespace bho
  674. bho::mysql::error_code bho::mysql::detail::deserialize_server_hello_impl(
  675. span<const std::uint8_t> msg,
  676. server_hello& output
  677. )
  678. {
  679. struct server_hello_packet
  680. {
  681. // int<1> protocol version Always 10
  682. string_null server_version;
  683. std::uint32_t connection_id;
  684. string_fixed<server_hello_auth1_length> auth_plugin_data_part_1;
  685. std::uint8_t filler; // should be 0
  686. string_fixed<2> capability_flags_low;
  687. std::uint8_t character_set; // default server a_protocol_character_set, only the lower 8-bits
  688. std::uint16_t status_flags; // server_status_flags
  689. string_fixed<2> capability_flags_high;
  690. std::uint8_t auth_plugin_data_len;
  691. string_fixed<10> reserved;
  692. // auth plugin data, 2nd part. This has a weird representation that doesn't fit any defined type
  693. string_null auth_plugin_name;
  694. } pack{};
  695. deserialization_context ctx(msg);
  696. auto err = deserialize(
  697. ctx,
  698. pack.server_version,
  699. pack.connection_id,
  700. pack.auth_plugin_data_part_1,
  701. pack.filler,
  702. pack.capability_flags_low,
  703. pack.character_set,
  704. pack.status_flags,
  705. pack.capability_flags_high
  706. );
  707. if (err != deserialize_errc::ok)
  708. return to_error_code(err);
  709. // Compose capabilities
  710. auto cap = compose_capabilities(pack.capability_flags_low, pack.capability_flags_high);
  711. // Check minimum server capabilities to deserialize this frame
  712. if (!cap.has(CLIENT_PLUGIN_AUTH))
  713. return client_errc::server_unsupported;
  714. // Deserialize next fields
  715. err = deserialize(ctx, pack.auth_plugin_data_len, pack.reserved);
  716. if (err != deserialize_errc::ok)
  717. return to_error_code(err);
  718. // Auth plugin data, second part
  719. auto auth2_length = static_cast<std::uint8_t>(
  720. (std::max)(13, pack.auth_plugin_data_len - server_hello_auth1_length)
  721. );
  722. const void* auth2_data = ctx.first();
  723. if (!ctx.enough_size(auth2_length))
  724. return client_errc::incomplete_message;
  725. ctx.advance(auth2_length);
  726. // Auth plugin name
  727. err = deserialize(ctx, pack.auth_plugin_name);
  728. if (err != deserialize_errc::ok)
  729. return to_error_code(err);
  730. // Compose output
  731. output.server = parse_db_version(pack.server_version.value);
  732. output.server_capabilities = cap;
  733. output.auth_plugin_name = pack.auth_plugin_name.value;
  734. // Compose auth_plugin_data
  735. output.auth_plugin_data.clear();
  736. output.auth_plugin_data.append(pack.auth_plugin_data_part_1.value.data(), server_hello_auth1_length);
  737. output.auth_plugin_data.append(auth2_data,
  738. auth2_length - 1); // discard an extra trailing NULL byte
  739. return ctx.check_extra_bytes();
  740. }
  741. bho::mysql::error_code bho::mysql::detail::deserialize_server_hello(
  742. span<const std::uint8_t> msg,
  743. server_hello& output,
  744. diagnostics& diag
  745. )
  746. {
  747. deserialization_context ctx(msg);
  748. // Message type
  749. std::uint8_t msg_type = 0;
  750. auto err = to_error_code(deserialize(ctx, msg_type));
  751. if (err)
  752. return err;
  753. if (msg_type == handshake_protocol_version_9)
  754. {
  755. return make_error_code(client_errc::server_unsupported);
  756. }
  757. else if (msg_type == error_packet_header)
  758. {
  759. // We don't know which DB is yet
  760. return process_error_packet(ctx.to_span(), db_flavor::mysql, diag);
  761. }
  762. else if (msg_type != handshake_protocol_version_10)
  763. {
  764. return make_error_code(client_errc::protocol_value_error);
  765. }
  766. else
  767. {
  768. return deserialize_server_hello_impl(ctx.to_span(), output);
  769. }
  770. }
  771. // Login request
  772. namespace bho {
  773. namespace mysql {
  774. namespace detail {
  775. BHO_MYSQL_STATIC_OR_INLINE
  776. std::uint8_t get_collation_first_byte(std::uint32_t collation_id) noexcept
  777. {
  778. return static_cast<std::uint8_t>(collation_id % 0xff);
  779. }
  780. struct login_request_packet
  781. {
  782. std::uint32_t client_flag; // capabilities
  783. std::uint32_t max_packet_size;
  784. std::uint8_t character_set; // collation ID first byte
  785. string_fixed<23> filler; // All 0s.
  786. string_null username;
  787. string_lenenc auth_response; // we require CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA
  788. string_null database; // only to be serialized if CLIENT_CONNECT_WITH_DB
  789. string_null client_plugin_name; // we require CLIENT_PLUGIN_AUTH
  790. // CLIENT_CONNECT_ATTRS: not implemented
  791. };
  792. BHO_MYSQL_STATIC_OR_INLINE
  793. login_request_packet to_packet(const login_request& req) noexcept
  794. {
  795. return {
  796. req.negotiated_capabilities.get(),
  797. req.max_packet_size,
  798. get_collation_first_byte(req.collation_id),
  799. {},
  800. string_null{req.username},
  801. string_lenenc{to_string(req.auth_response)},
  802. string_null{req.database},
  803. string_null{req.auth_plugin_name},
  804. };
  805. }
  806. } // namespace detail
  807. } // namespace mysql
  808. } // namespace bho
  809. std::size_t bho::mysql::detail::login_request::get_size() const noexcept
  810. {
  811. auto pack = to_packet(*this);
  812. return ::bho::mysql::detail::get_size(
  813. pack.client_flag,
  814. pack.max_packet_size,
  815. pack.character_set,
  816. pack.filler,
  817. pack.username,
  818. pack.auth_response
  819. ) +
  820. (negotiated_capabilities.has(CLIENT_CONNECT_WITH_DB)
  821. ? ::bho::mysql::detail::get_size(pack.database)
  822. : 0) +
  823. ::bho::mysql::detail::get_size(pack.client_plugin_name);
  824. }
  825. void bho::mysql::detail::login_request::serialize(span<std::uint8_t> buff) const noexcept
  826. {
  827. BHO_ASSERT(buff.size() >= get_size());
  828. serialization_context ctx(buff.data());
  829. auto pack = to_packet(*this);
  830. ::bho::mysql::detail::serialize(
  831. ctx,
  832. pack.client_flag,
  833. pack.max_packet_size,
  834. pack.character_set,
  835. pack.filler,
  836. pack.username,
  837. pack.auth_response
  838. );
  839. if (negotiated_capabilities.has(CLIENT_CONNECT_WITH_DB))
  840. {
  841. ::bho::mysql::detail::serialize(ctx, pack.database);
  842. }
  843. ::bho::mysql::detail::serialize(ctx, pack.client_plugin_name);
  844. }
  845. // ssl_request
  846. std::size_t bho::mysql::detail::ssl_request::get_size() const noexcept { return 4 + 4 + 1 + 23; }
  847. void bho::mysql::detail::ssl_request::serialize(span<std::uint8_t> buff) const noexcept
  848. {
  849. BHO_ASSERT(buff.size() >= get_size());
  850. serialization_context ctx(buff.data());
  851. struct ssl_request_packet
  852. {
  853. std::uint32_t client_flag;
  854. std::uint32_t max_packet_size;
  855. std::uint8_t character_set;
  856. string_fixed<23> filler;
  857. } pack{
  858. negotiated_capabilities.get(),
  859. max_packet_size,
  860. get_collation_first_byte(collation_id),
  861. {},
  862. };
  863. ::bho::mysql::detail::serialize(
  864. ctx,
  865. pack.client_flag,
  866. pack.max_packet_size,
  867. pack.character_set,
  868. pack.filler
  869. );
  870. }
  871. // auth_switch
  872. BHO_ATTRIBUTE_NODISCARD
  873. bho::mysql::error_code bho::mysql::detail::deserialize_auth_switch(
  874. span<const std::uint8_t> msg,
  875. auth_switch& output
  876. ) noexcept
  877. {
  878. struct auth_switch_request_packet
  879. {
  880. string_null plugin_name;
  881. string_eof auth_plugin_data;
  882. } pack{};
  883. deserialization_context ctx(msg);
  884. auto err = deserialize(ctx, pack.plugin_name, pack.auth_plugin_data);
  885. if (err != deserialize_errc::ok)
  886. return to_error_code(err);
  887. // Discard an additional NULL at the end of auth data
  888. string_view auth_data = pack.auth_plugin_data.value;
  889. if (!auth_data.empty() && auth_data.back() == 0)
  890. {
  891. auth_data = auth_data.substr(0, auth_data.size() - 1);
  892. }
  893. output = {
  894. pack.plugin_name.value,
  895. to_span(auth_data),
  896. };
  897. return ctx.check_extra_bytes();
  898. }
  899. bho::mysql::detail::handhake_server_response bho::mysql::detail::deserialize_handshake_server_response(
  900. span<const std::uint8_t> buff,
  901. db_flavor flavor,
  902. diagnostics& diag
  903. )
  904. {
  905. deserialization_context ctx(buff);
  906. std::uint8_t msg_type = 0;
  907. auto err = to_error_code(deserialize(ctx, msg_type));
  908. if (err)
  909. return err;
  910. if (msg_type == ok_packet_header)
  911. {
  912. ok_view ok{};
  913. err = deserialize_ok_packet(ctx.to_span(), ok);
  914. if (err)
  915. return err;
  916. return ok;
  917. }
  918. else if (msg_type == error_packet_header)
  919. {
  920. return process_error_packet(ctx.to_span(), flavor, diag);
  921. }
  922. else if (msg_type == auth_switch_request_header)
  923. {
  924. // We have received an auth switch request. Deserialize it
  925. auth_switch auth_sw{};
  926. auto err = deserialize_auth_switch(ctx.to_span(), auth_sw);
  927. if (err)
  928. return err;
  929. return auth_sw;
  930. }
  931. else if (msg_type == auth_more_data_header)
  932. {
  933. // We have received an auth more data request. Deserialize it.
  934. // Note that string_eof never fails deserialization (by definition)
  935. string_eof auth_more_data;
  936. auto err = deserialize(ctx, auth_more_data);
  937. BHO_ASSERT(err == deserialize_errc::ok);
  938. bho::ignore_unused(err);
  939. // If the special value fast_auth_complete_challenge
  940. // is received as auth data, it means that the auth is complete
  941. // but we must wait for another OK message. We consider this
  942. // a special type of message
  943. string_view challenge = auth_more_data.value;
  944. if (challenge == fast_auth_complete_challenge)
  945. {
  946. return handhake_server_response::ok_follows_t();
  947. }
  948. // Otherwise, just return the normal data
  949. return handhake_server_response(to_span(challenge));
  950. }
  951. else
  952. {
  953. // Unknown message type
  954. return make_error_code(client_errc::protocol_value_error);
  955. }
  956. }
  957. std::size_t bho::mysql::detail::auth_switch_response::get_size() const noexcept
  958. {
  959. return ::bho::mysql::detail::get_size(string_eof{to_string(auth_plugin_data)});
  960. }
  961. void bho::mysql::detail::auth_switch_response::serialize(span<std::uint8_t> buff) const noexcept
  962. {
  963. BHO_ASSERT(buff.size() >= get_size());
  964. serialization_context ctx(buff.data());
  965. ::bho::mysql::detail::serialize(ctx, string_eof{to_string(auth_plugin_data)});
  966. }
  967. #endif