using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Globalization; namespace Renci.SshNet.Common { /// /// Base ssh data serialization type /// public abstract class SshData { private static Encoding _ascii = new ASCIIEncoding(); #if SILVERLIGHT private static Encoding _utf8 = Encoding.UTF8; #else private static Encoding _utf8 = Encoding.Default; #endif /// /// Data byte array that hold message unencrypted data /// private List _data; private int _readerIndex; /// /// Gets a value indicating whether all data from the buffer has been read. /// /// /// true if this instance is end of data; otherwise, false. /// public bool IsEndOfData { get { return this._readerIndex >= this._data.Count(); } } private byte[] _loadedData; /// /// Gets the index that represents zero in current data type. /// /// /// The index of the zero reader. /// protected virtual int ZeroReaderIndex { get { return 0; } } /// /// Gets data bytes array /// /// public virtual byte[] GetBytes() { this._data = new List(); this.SaveData(); return this._data.ToArray(); } internal T OfType() where T : SshData, new() { var result = new T(); result.LoadBytes(this._loadedData); result.LoadData(); return result; } /// /// Loads data from specified bytes. /// /// Bytes array. /// is null. public void Load(byte[] value) { if (value == null) throw new ArgumentNullException("value"); this.LoadBytes(value); this.LoadData(); } /// /// Called when type specific data need to be loaded. /// protected abstract void LoadData(); /// /// Called when type specific data need to be saved. /// protected abstract void SaveData(); /// /// Loads data bytes into internal buffer. /// /// The bytes. /// is null. protected void LoadBytes(byte[] bytes) { // Note about why I check for null here, and in Load(byte[]) in this class. // This method is called by several other classes, such as SshNet.Messages.Message, SshNet.Sftp.SftpMessage. if (bytes == null) throw new ArgumentNullException("bytes"); this.ResetReader(); this._loadedData = bytes; this._data = new List(bytes); } /// /// Resets internal data reader index. /// protected void ResetReader() { this._readerIndex = this.ZeroReaderIndex; // Set to 1 to skip first byte which specifies message type } /// /// Reads all data left in internal buffer at current position. /// /// An array of bytes containing the remaining data in the internal buffer. protected byte[] ReadBytes() { var data = new byte[this._data.Count - this._readerIndex]; this._data.CopyTo(this._readerIndex, data, 0, data.Length); return data; } /// /// Reads next specified number of bytes data type from internal buffer. /// /// Number of bytes to read. /// An array of bytes that was read from the internal buffer. /// is greater than the internal buffer size. protected byte[] ReadBytes(int length) { // Note that this also prevents allocating non-relevant lengths, such as if length is greater than _data.Count but less than int.MaxValue. // For the nerds, the condition translates to: if (length > data.Count && length < int.MaxValue) // Which probably would cause all sorts of exception, most notably OutOfMemoryException. if (length > this._data.Count) throw new ArgumentOutOfRangeException("length"); var result = new byte[length]; this._data.CopyTo(this._readerIndex, result, 0, length); this._readerIndex += length; return result; } /// /// Reads next byte data type from internal buffer. /// /// Byte read. protected byte ReadByte() { return this.ReadBytes(1).FirstOrDefault(); } /// /// Reads next boolean data type from internal buffer. /// /// Boolean read. protected bool ReadBoolean() { return this.ReadByte() == 0 ? false : true; } /// /// Reads next uint16 data type from internal buffer. /// /// uint16 read protected UInt16 ReadUInt16() { var data = this.ReadBytes(2); return (ushort)(data[0] << 8 | data[1]); } /// /// Reads next uint32 data type from internal buffer. /// /// uint32 read protected UInt32 ReadUInt32() { var data = this.ReadBytes(4); return (uint)(data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]); } /// /// Reads next uint64 data type from internal buffer. /// /// uint64 read protected UInt64 ReadUInt64() { var data = this.ReadBytes(8); return (uint)(data[0] << 56 | data[1] << 48 | data[2] << 40 | data[3] << 32 | data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]); } /// /// Reads next int64 data type from internal buffer. /// /// int64 read protected Int64 ReadInt64() { var data = this.ReadBytes(8); return (int)(data[0] << 56 | data[1] << 48 | data[2] << 40 | data[3] << 32 | data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]); } /// /// Reads next string data type from internal buffer. /// /// string read protected string ReadAsciiString() { var length = this.ReadUInt32(); if (length > (uint)int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return _ascii.GetString(this.ReadBytes((int)length), 0, (int)length); } /// /// Reads next string data type from internal buffer. /// /// string read protected string ReadString() { var length = this.ReadUInt32(); if (length > (uint)int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return _utf8.GetString(this.ReadBytes((int)length), 0, (int)length); } /// /// Reads next string data type from internal buffer. /// /// string read protected byte[] ReadBinaryString() { var length = this.ReadUInt32(); if (length > (uint)int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return this.ReadBytes((int)length); } /// /// Reads next mpint data type from internal buffer. /// /// mpint read. protected BigInteger ReadBigInt() { var length = this.ReadUInt32(); var data = this.ReadBytes((int)length); return new BigInteger(data.Reverse().ToArray()); } /// /// Reads next name-list data type from internal buffer. /// /// protected string[] ReadNamesList() { var namesList = this.ReadString(); return namesList.Split(','); } /// /// Reads next extension-pair data type from internal buffer. /// /// protected IDictionary ReadExtensionPair() { Dictionary result = new Dictionary(); while (this._readerIndex < this._data.Count) { var extensionName = this.ReadString(); var extensionData = this.ReadString(); result.Add(extensionName, extensionData); } return result; } /// /// Writes bytes array data into internal buffer. /// /// Byte array data to write. /// is null. protected void Write(IEnumerable data) { this._data.AddRange(data); } /// /// Writes byte data into internal buffer. /// /// Byte data to write. protected void Write(byte data) { this._data.Add(data); } /// /// Writes boolean data into internal buffer. /// /// Boolean data to write. protected void Write(bool data) { if (data) { this.Write(1); } else { this.Write(0); } } /// /// Writes uint16 data into internal buffer. /// /// uint16 data to write. protected void Write(UInt16 data) { this.Write(data.GetBytes()); } /// /// Writes uint32 data into internal buffer. /// /// uint32 data to write. protected void Write(UInt32 data) { this.Write(data.GetBytes()); } /// /// Writes uint64 data into internal buffer. /// /// uint64 data to write. protected void Write(UInt64 data) { this.Write(data.GetBytes()); } /// /// Writes int64 data into internal buffer. /// /// int64 data to write. protected void Write(Int64 data) { this.Write(data.GetBytes()); } /// /// Writes string data into internal buffer using default encoding. /// /// string data to write. /// is null. protected void Write(string data) { if (data == null) throw new ArgumentNullException("data"); var bytes = _utf8.GetBytes(data); this.Write((uint)bytes.Length); this.Write(bytes); } /// /// Writes string data into internal buffer as ASCII. /// /// string data to write. protected void WriteAscii(string data) { if (data == null) throw new ArgumentNullException("data"); var bytes = _ascii.GetBytes(data); this.Write((uint)bytes.Length); this.Write(bytes); } /// /// Writes string data into internal buffer. /// /// string data to write. /// is null. protected void WriteBinaryString(byte[] data) { if (data == null) throw new ArgumentNullException("data"); this.Write((uint)data.Length); this._data.AddRange(data); } /// /// Writes mpint data into internal buffer. /// /// mpint data to write. protected void Write(BigInteger data) { var bytes = data.ToByteArray().Reverse().ToList(); this.Write((uint)bytes.Count); this.Write(bytes); } /// /// Writes name-list data into internal buffer. /// /// name-list data to write. protected void Write(string[] data) { this.WriteAscii(string.Join(",", data)); } /// /// Writes extension-pair data into internal buffer. /// /// extension-pair data to write. protected void Write(IDictionary data) { foreach (var item in data) { this.WriteAscii(item.Key); this.WriteAscii(item.Value); } } } }