You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

447 lines
14 KiB

4 years ago
  1. #if MYSQL_6_9
  2. // Copyright © 2013, Oracle and/or its affiliates. All rights reserved.
  3. //
  4. // MySQL Connector/NET is licensed under the terms of the GPLv2
  5. // <http://www.gnu.org/licenses/old-licenses/gpl-2.0.html>, like most
  6. // MySQL Connectors. There are special exceptions to the terms and
  7. // conditions of the GPLv2 as it is applied to this software, see the
  8. // FLOSS License Exception
  9. // <http://www.mysql.com/about/legal/licensing/foss-exception.html>.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published
  13. // by the Free Software Foundation; version 2 of the License.
  14. //
  15. // This program is distributed in the hope that it will be useful, but
  16. // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  17. // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  18. // for more details.
  19. //
  20. // You should have received a copy of the GNU General Public License along
  21. // with this program; if not, write to the Free Software Foundation, Inc.,
  22. // 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  23. namespace Externals.MySql.Data.MySqlClient.Memcached
  24. {
  25. using System;
  26. using System.Collections.Generic;
  27. using System.Text;
  28. using System.IO;
  29. /// <summary>
  30. /// Implementation of memcached binary client protocol.
  31. /// </summary>
  32. /// <remarks>According to http://code.google.com/p/memcached/wiki/BinaryProtocolRevamped </remarks>
  33. internal class BinaryClient : Client
  34. {
  35. private Encoding encoding;
  36. private enum OpCodes : byte
  37. {
  38. Get = 0x00,
  39. Set = 0x01,
  40. Add = 0x02,
  41. Replace = 0x03,
  42. Delete = 0x04,
  43. Increment = 0x05,
  44. Decrement = 0x06,
  45. Quit = 0x07,
  46. Flush = 0x08,
  47. GetK = 0x0c,
  48. GetKQ = 0x0d,
  49. Append = 0x0e,
  50. Prepend = 0x0f,
  51. SASL_list_mechs = 0x20,
  52. SASL_Auth = 0x21,
  53. SASL_Step = 0x22
  54. }
  55. private enum MagicByte : byte
  56. {
  57. Request = 0x80,
  58. Response = 0x81
  59. }
  60. private enum ResponseStatus : ushort
  61. {
  62. NoError = 0x0000,
  63. KeyNotFound = 0x0001,
  64. KeyExists = 0x0002,
  65. ValueTooLarge = 0x0003,
  66. InvalidArguments = 0x0004,
  67. ItemNotStored = 0x0005,
  68. IncrDecrOnNonNumericValue = 0x0006,
  69. VbucketBelongsToAnotherServer = 0x0007,
  70. AuthenticationError = 0x0008,
  71. AuthenticationContinue = 0x0009,
  72. UnknownCommand = 0x0081,
  73. OutOfMemory = 0x0082,
  74. NotSupported = 0x0083,
  75. InternalError = 0x0084,
  76. Busy = 0x0085,
  77. TemporaryFailure = 0x0086
  78. }
  79. public BinaryClient( string server, uint port ) : base( server, port )
  80. {
  81. encoding = Encoding.UTF8;
  82. }
  83. #region Memcached protocol interface
  84. public override void Add(string key, object data, TimeSpan expiration)
  85. {
  86. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Add, key, data, expiration, true);
  87. }
  88. public override void Append(string key, object data)
  89. {
  90. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Append, key, data, TimeSpan.Zero, false);
  91. }
  92. public override void Cas(string key, object data, TimeSpan expiration, ulong casUnique)
  93. {
  94. throw new NotImplementedException("Not available in binary protocol");
  95. //SendCommand((byte)MagicByte.Request, (byte)OpCodes.Cas, key, data, expiration, true, casUnique);
  96. }
  97. public override void Decrement(string key, int amount)
  98. {
  99. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Decrement, key, amount);
  100. }
  101. public override void Delete(string key)
  102. {
  103. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Delete, key );
  104. }
  105. public override void FlushAll(TimeSpan delay)
  106. {
  107. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Flush, delay);
  108. }
  109. public override KeyValuePair<string, object> Get(string key)
  110. {
  111. string val;
  112. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Get, key, out val );
  113. return new KeyValuePair<string, object>(key, val);
  114. }
  115. public override void Increment(string key, int amount)
  116. {
  117. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Increment, key, amount);
  118. }
  119. public override void Prepend(string key, object data)
  120. {
  121. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Prepend, key, data, TimeSpan.Zero, false);
  122. }
  123. public override void Replace(string key, object data, TimeSpan expiration)
  124. {
  125. SendCommand((byte)MagicByte.Request, (byte)OpCodes.Replace, key, data, expiration, true);
  126. }
  127. public override void Set(string key, object data, TimeSpan expiration)
  128. {
  129. SendCommand( ( byte )MagicByte.Request, ( byte )OpCodes.Set, key, data, expiration, true );
  130. }
  131. #endregion
  132. /// <summary>
  133. /// Sends an store command (add, replace, set).
  134. /// </summary>
  135. private void SendCommand(
  136. byte magic, byte opcode, string key, object data, TimeSpan expiration, bool hasExtra )
  137. {
  138. // Send data
  139. byte[] dataToSend = EncodeStoreCommand(magic, opcode, key, data, expiration, hasExtra );
  140. stream.Write(dataToSend, 0, dataToSend.Length);
  141. byte[] res = GetResponse();
  142. }
  143. /// <summary>
  144. /// Sends a get command.
  145. /// </summary>
  146. /// <param name="magic"></param>
  147. /// <param name="opcode"></param>
  148. /// <param name="key"></param>
  149. /// <param name="value"></param>
  150. private void SendCommand(byte magic, byte opcode, string key, out string value)
  151. {
  152. // Send data
  153. byte[] dataToSend = EncodeGetCommand(magic, opcode, key );
  154. stream.Write(dataToSend, 0, dataToSend.Length);
  155. byte[] res = GetResponse();
  156. byte[] bValue = new byte[res[4] - 4];
  157. Array.Copy(res, 28, bValue, 0, res[4] - 4);
  158. value = encoding.GetString(bValue, 0, bValue.Length);
  159. }
  160. /// <summary>
  161. /// Sends a delete command.
  162. /// </summary>
  163. private void SendCommand(byte magic, byte opcode, string key )
  164. {
  165. // Send data
  166. byte[] dataToSend = EncodeGetCommand(magic, opcode, key );
  167. stream.Write(dataToSend, 0, dataToSend.Length);
  168. byte[] res = GetResponse();
  169. }
  170. /// <summary>
  171. /// Sends a command without args (like flush).
  172. /// </summary>
  173. private void SendCommand(byte magic, byte opcode, TimeSpan expiration )
  174. {
  175. // Send data
  176. byte[] dataToSend = EncodeFlushCommand(magic, opcode, expiration);
  177. stream.Write(dataToSend, 0, dataToSend.Length);
  178. byte[] res = GetResponse();
  179. }
  180. /// <summary>
  181. /// Sends a command with amount (INCR/DECR)
  182. /// </summary>
  183. private void SendCommand(byte magic, byte opcode, string key, int amount )
  184. {
  185. // Send data
  186. byte[] dataToSend = EncodeIncrCommand(magic, opcode, key, amount);
  187. stream.Write(dataToSend, 0, dataToSend.Length);
  188. byte[] res = GetResponse();
  189. }
  190. private byte[] GetResponse()
  191. {
  192. byte[] response = new byte[24];
  193. stream.Read(response, 0, response.Length);
  194. ValidateResponse( response );
  195. return response;
  196. }
  197. private void ValidateResponse(byte[] res)
  198. {
  199. // Memcached returns words in big endian.
  200. ushort status = (ushort)(( res[ 6 ] << 8 ) | res[ 7 ] );
  201. if( status != 0 )
  202. {
  203. throw new MemcachedException(((ResponseStatus)status).ToString());
  204. }
  205. }
  206. /// <summary>
  207. /// Encodes in the binary protocol the a command of the kind set, add or replace.
  208. /// </summary>
  209. /// <param name="magic"></param>
  210. /// <param name="opcode"></param>
  211. /// <param name="key"></param>
  212. /// <param name="data"></param>
  213. /// <param name="expiration"></param>
  214. /// <param name="hasExtra">If true applies to set, add or replace commands; if false applies to append and prepend commands.</param>
  215. /// <returns></returns>
  216. private byte[] EncodeStoreCommand(
  217. byte magic, byte opcode, string key, object data, TimeSpan expiration,
  218. bool hasExtra )
  219. {
  220. /*
  221. * Field (offset) (value)
  222. Magic (0) : 0x80
  223. Opcode (1) : 0x02
  224. Key length (2,3) : 0x0005
  225. Extra length (4) : 0x08
  226. Data type (5) : 0x00
  227. VBucket (6,7) : 0x0000
  228. Total body (8-11) : 0x00000012
  229. Opaque (12-15): 0x00000000
  230. CAS (16-23): 0x0000000000000000
  231. Extras :
  232. Flags (24-27): 0xdeadbeef
  233. Expiry (28-31): 0x00000e10
  234. Key (32-36): The textual string "Hello"
  235. Value (37-41): The textual string "World"
  236. * */
  237. byte[] bKey = encoding.GetBytes(key);
  238. byte[] bData = encoding.GetBytes(data.ToString());
  239. MemoryStream ms = new MemoryStream();
  240. // write magic
  241. ms.WriteByte(magic);
  242. // write opcode
  243. ms.WriteByte(opcode);
  244. // write keylength
  245. WriteToMemoryStream(BitConverter.GetBytes((ushort)bKey.Length), ms);
  246. // write extra length
  247. ms.WriteByte(8);
  248. // write data type
  249. ms.WriteByte(0);
  250. // write status
  251. ms.WriteByte(0); ms.WriteByte(0);
  252. // write total body length
  253. WriteToMemoryStream(BitConverter.GetBytes((uint)
  254. (bKey.Length + bData.Length + ( hasExtra? 8 : 0))), ms);
  255. // write opaque
  256. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  257. // write CAS
  258. // NOTE: For some reason in the Innodb implementation of Memcached the CAS
  259. // is 4 bytes long (instead of 8 bytes).
  260. WriteToMemoryStream(BitConverter.GetBytes((ushort)0), ms);
  261. // write extras, flags
  262. if (hasExtra)
  263. {
  264. ms.Write(new byte[4], 0, 4);
  265. WriteToMemoryStream(BitConverter.GetBytes((uint)(expiration.TotalSeconds)), ms);
  266. }
  267. // write key
  268. ms.Write(bKey, 0, bKey.Length);
  269. // write value
  270. ms.Write(bData, 0, bData.Length);
  271. return ms.ToArray();
  272. }
  273. private byte[] EncodeGetCommand(byte magic, byte opcode, string key )
  274. {
  275. /*
  276. * Field (offset) (value)
  277. Magic (0) : 0x80
  278. Opcode (1) : 0x00
  279. Key length (2,3) : 0x0005
  280. Extra length (4) : 0x00
  281. Data type (5) : 0x00
  282. VBucket (6,7) : 0x0000
  283. Total body (8-11) : 0x00000005
  284. Opaque (12-15): 0x00000000
  285. CAS (16-23): 0x0000000000000000
  286. Extras : None
  287. Key (24-29): The textual string: "Hello"
  288. Value : None
  289. * */
  290. byte[] bKey = encoding.GetBytes(key);
  291. MemoryStream ms = new MemoryStream();
  292. // write magic
  293. ms.WriteByte(magic);
  294. // write opcode
  295. ms.WriteByte(opcode);
  296. // write keylength
  297. WriteToMemoryStream(BitConverter.GetBytes((ushort)bKey.Length), ms);
  298. // write extra length
  299. ms.WriteByte(8);
  300. // write data type
  301. ms.WriteByte(0);
  302. // write status
  303. ms.WriteByte(0); ms.WriteByte(0);
  304. // write total body length
  305. WriteToMemoryStream(BitConverter.GetBytes((ushort)bKey.Length), ms);
  306. // write opaque
  307. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  308. // write CAS
  309. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  310. // write key
  311. ms.Write(bKey, 0, bKey.Length);
  312. return ms.ToArray();
  313. }
  314. private byte[] EncodeFlushCommand(byte magic, byte opcode, TimeSpan expiration )
  315. {
  316. /*
  317. * Field (offset) (value)
  318. Magic (0) : 0x80
  319. Opcode (1) : 0x08
  320. Key length (2,3) : 0x0000
  321. Extra length (4) : 0x04
  322. Data type (5) : 0x00
  323. VBucket (6,7) : 0x0000
  324. Total body (8-11) : 0x00000004
  325. Opaque (12-15): 0x00000000
  326. CAS (16-23): 0x0000000000000000
  327. Extras :
  328. Expiry (24-27): 0x000e10
  329. Key : None
  330. Value : None
  331. * */
  332. MemoryStream ms = new MemoryStream();
  333. // write magic
  334. ms.WriteByte(magic);
  335. // write opcode
  336. ms.WriteByte(opcode);
  337. // write keylength
  338. ms.WriteByte(0); ms.WriteByte(0);
  339. // write extra length
  340. ms.WriteByte(4);
  341. // write data type
  342. ms.WriteByte(0);
  343. // write status
  344. ms.WriteByte(0); ms.WriteByte(0);
  345. // write total body length
  346. WriteToMemoryStream(BitConverter.GetBytes((ushort)4), ms);
  347. // write opaque
  348. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  349. // write CAS
  350. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  351. // write extra (flags)
  352. WriteToMemoryStream(BitConverter.GetBytes((uint)(expiration.TotalSeconds)), ms);
  353. return ms.ToArray();
  354. }
  355. private byte[] EncodeIncrCommand(byte magic, byte opcode, string key, int amount )
  356. {
  357. /*
  358. * Field (offset) (value)
  359. Magic (0) : 0x80
  360. Opcode (1) : 0x05
  361. Key length (2,3) : 0x0007
  362. Extra length (4) : 0x14
  363. Data type (5) : 0x00
  364. VBucket (6,7) : 0x0000
  365. Total body (8-11) : 0x0000001b
  366. Opaque (12-15): 0x00000000
  367. CAS (16-23): 0x0000000000000000
  368. Extras :
  369. delta (24-31): 0x0000000000000001
  370. initial (32-39): 0x0000000000000000
  371. exipration (40-43): 0x00000e10
  372. Key : Textual string "counter"
  373. Value : None
  374. * */
  375. byte[] bKey = encoding.GetBytes(key);
  376. MemoryStream ms = new MemoryStream();
  377. // write magic
  378. ms.WriteByte(magic);
  379. // write opcode
  380. ms.WriteByte(opcode);
  381. // write keylength
  382. WriteToMemoryStream(BitConverter.GetBytes((ushort)bKey.Length), ms);
  383. // write extra length
  384. ms.WriteByte(20);
  385. // write data type
  386. ms.WriteByte(0);
  387. // write status
  388. ms.WriteByte(0); ms.WriteByte(0);
  389. // write total body length
  390. WriteToMemoryStream(BitConverter.GetBytes((ushort)( bKey.Length + 20 )), ms);
  391. // write opaque
  392. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  393. // write CAS
  394. WriteToMemoryStream(BitConverter.GetBytes((uint)0), ms);
  395. // write extra (flags)
  396. long delta = amount;
  397. if ((OpCodes)opcode == OpCodes.Decrement)
  398. delta *= -1;
  399. WriteToMemoryStream(BitConverter.GetBytes((long)0), ms);
  400. WriteToMemoryStream(BitConverter.GetBytes((uint)(TimeSpan.Zero.TotalSeconds)), ms);
  401. // write key
  402. ms.Write(bKey, 0, bKey.Length);
  403. return ms.ToArray();
  404. }
  405. private void WriteToMemoryStream(byte[] data, MemoryStream ms)
  406. {
  407. // .NET runs in x86 which uses little endian, and Memcached runs in big endian.
  408. Array.Reverse(data);
  409. ms.Write(data, 0, data.Length);
  410. }
  411. }
  412. }
  413. #endif