SshData.cs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. using System;
  2. using System.Collections.Generic;
  3. #if !TUNING
  4. using System.Linq;
  5. #endif
  6. using System.Text;
  7. using System.Globalization;
  8. namespace Renci.SshNet.Common
  9. {
  10. /// <summary>
  11. /// Base ssh data serialization type
  12. /// </summary>
  13. public abstract class SshData
  14. {
  15. internal const int DefaultCapacity = 64;
  16. internal static readonly Encoding Ascii = new ASCIIEncoding();
  17. #if SILVERLIGHT
  18. internal static readonly Encoding Utf8 = Encoding.UTF8;
  19. #else
  20. internal static readonly Encoding Utf8 = Encoding.Default;
  21. #endif
  22. #if TUNING
  23. private SshDataStream _stream;
  24. protected SshDataStream DataStream
  25. {
  26. get { return _stream; }
  27. }
  28. #else
  29. /// <summary>
  30. /// Data byte array that hold message unencrypted data
  31. /// </summary>
  32. private List<byte> _data;
  33. private int _readerIndex;
  34. #endif
  35. /// <summary>
  36. /// Gets a value indicating whether all data from the buffer has been read.
  37. /// </summary>
  38. /// <value>
  39. /// <c>true</c> if this instance is end of data; otherwise, <c>false</c>.
  40. /// </value>
  41. protected bool IsEndOfData
  42. {
  43. get
  44. {
  45. #if TUNING
  46. return _stream.Position >= _stream.Length;
  47. #else
  48. return _readerIndex >= _data.Count();
  49. #endif
  50. }
  51. }
  52. private byte[] _loadedData;
  53. #if TUNING
  54. private int _offset;
  55. #endif
  56. /// <summary>
  57. /// Gets the index that represents zero in current data type.
  58. /// </summary>
  59. /// <value>
  60. /// The index of the zero reader.
  61. /// </value>
  62. protected virtual int ZeroReaderIndex
  63. {
  64. get
  65. {
  66. return 0;
  67. }
  68. }
  69. #if TUNING
  70. /// <summary>
  71. /// Gets the size of the message in bytes.
  72. /// </summary>
  73. /// <value>
  74. /// The size of the messages in bytes.
  75. /// </value>
  76. protected virtual int BufferCapacity
  77. {
  78. get { return 0; }
  79. }
  80. #endif
  81. /// <summary>
  82. /// Gets data bytes array
  83. /// </summary>
  84. /// <returns>Byte array representation of data structure.</returns>
  85. public
  86. #if !TUNING
  87. virtual
  88. #endif
  89. byte[] GetBytes()
  90. {
  91. #if TUNING
  92. var messageLength = BufferCapacity;
  93. var capacity = messageLength != -1 ? messageLength : DefaultCapacity;
  94. var dataStream = new SshDataStream(capacity);
  95. WriteBytes(dataStream);
  96. return dataStream.ToArray();
  97. #else
  98. _data = new List<byte>();
  99. SaveData();
  100. return _data.ToArray();
  101. #endif
  102. }
  103. #if TUNING
  104. /// <summary>
  105. /// Writes the current message to the specified <see cref="SshDataStream"/>.
  106. /// </summary>
  107. /// <param name="stream">The <see cref="SshDataStream"/> to write the message to.</param>
  108. protected virtual void WriteBytes(SshDataStream stream)
  109. {
  110. _stream = stream;
  111. SaveData();
  112. }
  113. #endif
  114. internal T OfType<T>() where T : SshData, new()
  115. {
  116. var result = new T();
  117. #if TUNING
  118. result.LoadBytes(_loadedData, _offset);
  119. #else
  120. result.LoadBytes(_loadedData);
  121. #endif
  122. result.LoadData();
  123. return result;
  124. }
  125. /// <summary>
  126. /// Loads data from specified bytes.
  127. /// </summary>
  128. /// <param name="value">Bytes array.</param>
  129. /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
  130. public void Load(byte[] value)
  131. {
  132. #if TUNING
  133. Load(value, 0);
  134. #else
  135. if (value == null)
  136. throw new ArgumentNullException("value");
  137. LoadBytes(value);
  138. LoadData();
  139. #endif
  140. }
  141. #if TUNING
  142. /// <summary>
  143. /// Loads data from the specified buffer.
  144. /// </summary>
  145. /// <param name="value">Bytes array.</param>
  146. /// <param name="offset">The zero-based offset in <paramref name="value"/> at which to begin reading SSH data.</param>
  147. /// <exception cref="ArgumentNullException"><paramref name="value"/> is null.</exception>
  148. public void Load(byte[] value, int offset)
  149. {
  150. LoadBytes(value, offset);
  151. LoadData();
  152. }
  153. #endif
  154. /// <summary>
  155. /// Called when type specific data need to be loaded.
  156. /// </summary>
  157. protected abstract void LoadData();
  158. /// <summary>
  159. /// Called when type specific data need to be saved.
  160. /// </summary>
  161. protected abstract void SaveData();
  162. /// <summary>
  163. /// Loads data bytes into internal buffer.
  164. /// </summary>
  165. /// <param name="bytes">The bytes.</param>
  166. /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is null.</exception>
  167. protected void LoadBytes(byte[] bytes)
  168. {
  169. #if TUNING
  170. LoadBytes(bytes, 0);
  171. #else
  172. // Note about why I check for null here, and in Load(byte[]) in this class.
  173. // This method is called by several other classes, such as SshNet.Messages.Message, SshNet.Sftp.SftpMessage.
  174. if (bytes == null)
  175. throw new ArgumentNullException("bytes");
  176. ResetReader();
  177. _loadedData = bytes;
  178. _data = new List<byte>(bytes);
  179. #endif
  180. }
  181. #if TUNING
  182. /// <summary>
  183. /// Loads data bytes into internal buffer.
  184. /// </summary>
  185. /// <param name="bytes">The bytes.</param>
  186. /// <param name="offset">The zero-based offset in <paramref name="bytes"/> at which to begin reading SSH data.</param>
  187. /// <exception cref="ArgumentNullException"><paramref name="bytes"/> is null.</exception>
  188. protected void LoadBytes(byte[] bytes, int offset)
  189. {
  190. if (bytes == null)
  191. throw new ArgumentNullException("bytes");
  192. _loadedData = bytes;
  193. _offset = offset;
  194. _stream = new SshDataStream(bytes);
  195. ResetReader();
  196. }
  197. #endif
  198. /// <summary>
  199. /// Resets internal data reader index.
  200. /// </summary>
  201. protected void ResetReader()
  202. {
  203. #if TUNING
  204. _stream.Position = ZeroReaderIndex + _offset;
  205. #else
  206. _readerIndex = ZeroReaderIndex; // Set to 1 to skip first byte which specifies message type
  207. #endif
  208. }
  209. /// <summary>
  210. /// Reads all data left in internal buffer at current position.
  211. /// </summary>
  212. /// <returns>An array of bytes containing the remaining data in the internal buffer.</returns>
  213. protected byte[] ReadBytes()
  214. {
  215. #if TUNING
  216. var bytesLength = (int) (_stream.Length - _stream.Position);
  217. var data = new byte[bytesLength];
  218. _stream.Read(data, 0, bytesLength);
  219. return data;
  220. #else
  221. var data = new byte[_data.Count - _readerIndex];
  222. _data.CopyTo(_readerIndex, data, 0, data.Length);
  223. return data;
  224. #endif
  225. }
  226. /// <summary>
  227. /// Reads next specified number of bytes data type from internal buffer.
  228. /// </summary>
  229. /// <param name="length">Number of bytes to read.</param>
  230. /// <returns>An array of bytes that was read from the internal buffer.</returns>
  231. /// <exception cref="ArgumentOutOfRangeException"><paramref name="length"/> is greater than the internal buffer size.</exception>
  232. protected byte[] ReadBytes(int length)
  233. {
  234. // Note that this also prevents allocating non-relevant lengths, such as if length is greater than _data.Count but less than int.MaxValue.
  235. // For the nerds, the condition translates to: if (length > data.Count && length < int.MaxValue)
  236. // Which probably would cause all sorts of exception, most notably OutOfMemoryException.
  237. #if TUNING
  238. var data = new byte[length];
  239. var bytesRead = _stream.Read(data, 0, length);
  240. if (bytesRead < length)
  241. throw new ArgumentOutOfRangeException("length");
  242. return data;
  243. #else
  244. if (length > _data.Count)
  245. throw new ArgumentOutOfRangeException("length");
  246. var result = new byte[length];
  247. _data.CopyTo(_readerIndex, result, 0, length);
  248. _readerIndex += length;
  249. return result;
  250. #endif
  251. }
  252. /// <summary>
  253. /// Reads next byte data type from internal buffer.
  254. /// </summary>
  255. /// <returns>Byte read.</returns>
  256. protected byte ReadByte()
  257. {
  258. #if TUNING
  259. var byteRead = _stream.ReadByte();
  260. if (byteRead == -1)
  261. throw new InvalidOperationException("Attempt to read past the end of the SSH data stream.");
  262. return (byte) byteRead;
  263. #else
  264. return ReadBytes(1).FirstOrDefault();
  265. #endif
  266. }
  267. /// <summary>
  268. /// Reads next boolean data type from internal buffer.
  269. /// </summary>
  270. /// <returns>Boolean read.</returns>
  271. protected bool ReadBoolean()
  272. {
  273. return ReadByte() != 0;
  274. }
  275. /// <summary>
  276. /// Reads next uint16 data type from internal buffer.
  277. /// </summary>
  278. /// <returns>uint16 read</returns>
  279. protected ushort ReadUInt16()
  280. {
  281. var data = ReadBytes(2);
  282. return (ushort)(data[0] << 8 | data[1]);
  283. }
  284. /// <summary>
  285. /// Reads next uint32 data type from internal buffer.
  286. /// </summary>
  287. /// <returns>uint32 read</returns>
  288. protected uint ReadUInt32()
  289. {
  290. var data = ReadBytes(4);
  291. return (uint)(data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]);
  292. }
  293. /// <summary>
  294. /// Reads next uint64 data type from internal buffer.
  295. /// </summary>
  296. /// <returns>uint64 read</returns>
  297. protected ulong ReadUInt64()
  298. {
  299. var data = ReadBytes(8);
  300. return ((ulong)data[0] << 56 | (ulong)data[1] << 48 | (ulong)data[2] << 40 | (ulong)data[3] << 32 | (ulong)data[4] << 24 | (ulong)data[5] << 16 | (ulong)data[6] << 8 | data[7]);
  301. }
  302. #if !TUNING
  303. /// <summary>
  304. /// Reads next string data type from internal buffer.
  305. /// </summary>
  306. /// <returns>string read</returns>
  307. protected string ReadAsciiString()
  308. {
  309. var length = ReadUInt32();
  310. if (length > int.MaxValue)
  311. {
  312. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
  313. }
  314. return Ascii.GetString(ReadBytes((int)length), 0, (int)length);
  315. }
  316. #endif
  317. /// <summary>
  318. /// Reads next string data type from internal buffer.
  319. /// </summary>
  320. /// <returns>string read</returns>
  321. protected string ReadString()
  322. {
  323. return ReadString(Utf8);
  324. }
  325. /// <summary>
  326. /// Reads next string data type from internal buffer.
  327. /// </summary>
  328. /// <returns>string read</returns>
  329. protected string ReadString(Encoding encoding)
  330. {
  331. #if TUNING
  332. return _stream.ReadString(encoding);
  333. #else
  334. var length = ReadUInt32();
  335. if (length > int.MaxValue)
  336. {
  337. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
  338. }
  339. return encoding.GetString(ReadBytes((int)length), 0, (int)length);
  340. #endif
  341. }
  342. #if TUNING
  343. /// <summary>
  344. /// Reads next data type as byte array from internal buffer.
  345. /// </summary>
  346. /// <returns>
  347. /// The bytes read.
  348. /// </returns>
  349. protected byte[] ReadBinary()
  350. {
  351. return _stream.ReadBinary();
  352. }
  353. #else
  354. /// <summary>
  355. /// Reads next string data type from internal buffer.
  356. /// </summary>
  357. /// <returns>string read</returns>
  358. protected byte[] ReadBinaryString()
  359. {
  360. var length = ReadUInt32();
  361. if (length > int.MaxValue)
  362. {
  363. throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue));
  364. }
  365. return ReadBytes((int)length);
  366. }
  367. #endif
  368. /// <summary>
  369. /// Reads next name-list data type from internal buffer.
  370. /// </summary>
  371. /// <returns>String array or read data..</returns>
  372. protected string[] ReadNamesList()
  373. {
  374. var namesList = ReadString();
  375. return namesList.Split(',');
  376. }
  377. /// <summary>
  378. /// Reads next extension-pair data type from internal buffer.
  379. /// </summary>
  380. /// <returns>Extensions pair dictionary.</returns>
  381. protected IDictionary<string, string> ReadExtensionPair()
  382. {
  383. var result = new Dictionary<string, string>();
  384. while (!IsEndOfData)
  385. {
  386. var extensionName = ReadString();
  387. var extensionData = ReadString();
  388. result.Add(extensionName, extensionData);
  389. }
  390. return result;
  391. }
  392. #if TUNING
  393. /// <summary>
  394. /// Writes bytes array data into internal buffer.
  395. /// </summary>
  396. /// <param name="data">Byte array data to write.</param>
  397. /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception>
  398. protected void Write(byte[] data)
  399. {
  400. _stream.Write(data);
  401. }
  402. #else
  403. /// <summary>
  404. /// Writes bytes array data into internal buffer.
  405. /// </summary>
  406. /// <param name="data">Byte array data to write.</param>
  407. /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception>
  408. protected void Write(IEnumerable<byte> data)
  409. {
  410. _data.AddRange(data);
  411. }
  412. #endif
  413. #if TUNING
  414. /// <summary>
  415. /// Writes a sequence of bytes to the current SSH data stream and advances the current position
  416. /// within this stream by the number of bytes written.
  417. /// </summary>
  418. /// <param name="buffer">An array of bytes. This method write <paramref name="count"/> bytes from buffer to the current SSH data stream.</param>
  419. /// <param name="offset">The zero-based offset in <paramref name="buffer"/> at which to begin writing bytes to the SSH data stream.</param>
  420. /// <param name="count">The number of bytes to be written to the current SSH data stream.</param>
  421. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>
  422. /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length.</exception>
  423. /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
  424. protected void Write(byte[] buffer, int offset, int count)
  425. {
  426. _stream.Write(buffer, offset, count);
  427. }
  428. #endif
  429. /// <summary>
  430. /// Writes byte data into internal buffer.
  431. /// </summary>
  432. /// <param name="data">Byte data to write.</param>
  433. protected void Write(byte data)
  434. {
  435. #if TUNING
  436. _stream.WriteByte(data);
  437. #else
  438. _data.Add(data);
  439. #endif
  440. }
  441. /// <summary>
  442. /// Writes boolean data into internal buffer.
  443. /// </summary>
  444. /// <param name="data">Boolean data to write.</param>
  445. protected void Write(bool data)
  446. {
  447. Write(data ? (byte) 1 : (byte) 0);
  448. }
  449. /// <summary>
  450. /// Writes uint32 data into internal buffer.
  451. /// </summary>
  452. /// <param name="data">uint32 data to write.</param>
  453. protected void Write(uint data)
  454. {
  455. #if TUNING
  456. _stream.Write(data);
  457. #else
  458. Write(data.GetBytes());
  459. #endif
  460. }
  461. /// <summary>
  462. /// Writes uint64 data into internal buffer.
  463. /// </summary>
  464. /// <param name="data">uint64 data to write.</param>
  465. protected void Write(ulong data)
  466. {
  467. #if TUNING
  468. _stream.Write(data);
  469. #else
  470. Write(data.GetBytes());
  471. #endif
  472. }
  473. /// <summary>
  474. /// Writes string data into internal buffer using default encoding.
  475. /// </summary>
  476. /// <param name="data">string data to write.</param>
  477. /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception>
  478. protected void Write(string data)
  479. {
  480. Write(data, Utf8);
  481. }
  482. /// <summary>
  483. /// Writes string data into internal buffer using the specified encoding.
  484. /// </summary>
  485. /// <param name="data">string data to write.</param>
  486. /// <param name="encoding">The character encoding to use.</param>
  487. /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception>
  488. /// <exception cref="ArgumentNullException"><paramref name="encoding"/> is null.</exception>
  489. protected void Write(string data, Encoding encoding)
  490. {
  491. #if TUNING
  492. _stream.Write(data, encoding);
  493. #else
  494. if (data == null)
  495. throw new ArgumentNullException("data");
  496. if (encoding == null)
  497. throw new ArgumentNullException("encoding");
  498. var bytes = encoding.GetBytes(data);
  499. Write((uint)bytes.Length);
  500. Write(bytes);
  501. #endif
  502. }
  503. #if TUNING
  504. /// <summary>
  505. /// Writes data into internal buffer.
  506. /// </summary>
  507. /// <param name="buffer">The data to write.</param>
  508. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>
  509. protected void WriteBinaryString(byte[] buffer)
  510. {
  511. _stream.WriteBinary(buffer);
  512. }
  513. /// <summary>
  514. /// Writes data into internal buffer.
  515. /// </summary>
  516. /// <param name="buffer">An array of bytes. This method write <paramref name="count"/> bytes from buffer to the current SSH data stream.</param>
  517. /// <param name="offset">The zero-based byte offset in <paramref name="buffer"/> at which to begin writing bytes to the SSH data stream.</param>
  518. /// <param name="count">The number of bytes to be written to the current SSH data stream.</param>
  519. /// <exception cref="ArgumentNullException"><paramref name="buffer"/> is null.</exception>
  520. /// <exception cref="ArgumentException">The sum of <paramref name="offset"/> and <paramref name="count"/> is greater than the buffer length.</exception>
  521. /// <exception cref="ArgumentOutOfRangeException"><paramref name="offset"/> or <paramref name="count"/> is negative.</exception>
  522. protected void WriteBinary(byte[] buffer, int offset, int count)
  523. {
  524. _stream.WriteBinary(buffer, offset, count);
  525. }
  526. #else
  527. /// <summary>
  528. /// Writes string data into internal buffer.
  529. /// </summary>
  530. /// <param name="data">string data to write.</param>
  531. /// <exception cref="ArgumentNullException"><paramref name="data"/> is null.</exception>
  532. protected void WriteBinaryString(byte[] data)
  533. {
  534. if (data == null)
  535. throw new ArgumentNullException("data");
  536. Write((uint)data.Length);
  537. _data.AddRange(data);
  538. }
  539. #endif
  540. /// <summary>
  541. /// Writes mpint data into internal buffer.
  542. /// </summary>
  543. /// <param name="data">mpint data to write.</param>
  544. protected void Write(BigInteger data)
  545. {
  546. #if TUNING
  547. _stream.Write(data);
  548. #else
  549. var bytes = data.ToByteArray().Reverse().ToList();
  550. Write((uint)bytes.Count);
  551. Write(bytes);
  552. #endif
  553. }
  554. /// <summary>
  555. /// Writes name-list data into internal buffer.
  556. /// </summary>
  557. /// <param name="data">name-list data to write.</param>
  558. protected void Write(string[] data)
  559. {
  560. Write(string.Join(",", data), Ascii);
  561. }
  562. /// <summary>
  563. /// Writes extension-pair data into internal buffer.
  564. /// </summary>
  565. /// <param name="data">extension-pair data to write.</param>
  566. protected void Write(IDictionary<string, string> data)
  567. {
  568. foreach (var item in data)
  569. {
  570. Write(item.Key, Ascii);
  571. Write(item.Value, Ascii);
  572. }
  573. }
  574. }
  575. }