using System; using System.Collections.Generic; #if !TUNING using System.Linq; #endif using System.Text; using System.Globalization; namespace Renci.SshNet.Common { /// /// Base ssh data serialization type /// public abstract class SshData { internal const int DefaultCapacity = 64; internal static readonly Encoding Ascii = new ASCIIEncoding(); #if SILVERLIGHT internal static readonly Encoding Utf8 = Encoding.UTF8; #else internal static readonly Encoding Utf8 = Encoding.Default; #endif #if TUNING private SshDataStream _stream; protected SshDataStream DataStream { get { return _stream; } } #else /// /// Data byte array that hold message unencrypted data /// private List _data; private int _readerIndex; #endif /// /// Gets a value indicating whether all data from the buffer has been read. /// /// /// true if this instance is end of data; otherwise, false. /// protected bool IsEndOfData { get { #if TUNING return _stream.Position >= _stream.Length; #else return _readerIndex >= _data.Count(); #endif } } private byte[] _loadedData; #if TUNING private int _offset; #endif /// /// Gets the index that represents zero in current data type. /// /// /// The index of the zero reader. /// protected virtual int ZeroReaderIndex { get { return 0; } } #if TUNING /// /// Gets the size of the message in bytes. /// /// /// The size of the messages in bytes. /// protected virtual int BufferCapacity { get { return 0; } } #endif /// /// Gets data bytes array /// /// Byte array representation of data structure. public #if !TUNING virtual #endif byte[] GetBytes() { #if TUNING var messageLength = BufferCapacity; var capacity = messageLength != -1 ? messageLength : DefaultCapacity; var dataStream = new SshDataStream(capacity); WriteBytes(dataStream); return dataStream.ToArray(); #else _data = new List(); SaveData(); return _data.ToArray(); #endif } #if TUNING /// /// Writes the current message to the specified . /// /// The to write the message to. protected virtual void WriteBytes(SshDataStream stream) { _stream = stream; SaveData(); } #endif internal T OfType() where T : SshData, new() { var result = new T(); #if TUNING result.LoadBytes(_loadedData, _offset); #else result.LoadBytes(_loadedData); #endif result.LoadData(); return result; } /// /// Loads data from specified bytes. /// /// Bytes array. /// is null. public void Load(byte[] value) { #if TUNING Load(value, 0); #else if (value == null) throw new ArgumentNullException("value"); LoadBytes(value); LoadData(); #endif } #if TUNING /// /// Loads data from the specified buffer. /// /// Bytes array. /// The zero-based offset in at which to begin reading SSH data. /// is null. public void Load(byte[] value, int offset) { LoadBytes(value, offset); LoadData(); } #endif /// /// 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) { #if TUNING LoadBytes(bytes, 0); #else // 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"); ResetReader(); _loadedData = bytes; _data = new List(bytes); #endif } #if TUNING /// /// Loads data bytes into internal buffer. /// /// The bytes. /// The zero-based offset in at which to begin reading SSH data. /// is null. protected void LoadBytes(byte[] bytes, int offset) { if (bytes == null) throw new ArgumentNullException("bytes"); _loadedData = bytes; _offset = offset; _stream = new SshDataStream(bytes); ResetReader(); } #endif /// /// Resets internal data reader index. /// protected void ResetReader() { #if TUNING _stream.Position = ZeroReaderIndex + _offset; #else _readerIndex = ZeroReaderIndex; // Set to 1 to skip first byte which specifies message type #endif } /// /// 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() { #if TUNING var bytesLength = (int) (_stream.Length - _stream.Position); var data = new byte[bytesLength]; _stream.Read(data, 0, bytesLength); return data; #else var data = new byte[_data.Count - _readerIndex]; _data.CopyTo(_readerIndex, data, 0, data.Length); return data; #endif } /// /// 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 TUNING var data = new byte[length]; var bytesRead = _stream.Read(data, 0, length); if (bytesRead < length) throw new ArgumentOutOfRangeException("length"); return data; #else if (length > _data.Count) throw new ArgumentOutOfRangeException("length"); var result = new byte[length]; _data.CopyTo(_readerIndex, result, 0, length); _readerIndex += length; return result; #endif } /// /// Reads next byte data type from internal buffer. /// /// Byte read. protected byte ReadByte() { #if TUNING var byteRead = _stream.ReadByte(); if (byteRead == -1) throw new InvalidOperationException("Attempt to read past the end of the SSH data stream."); return (byte) byteRead; #else return ReadBytes(1).FirstOrDefault(); #endif } /// /// Reads next boolean data type from internal buffer. /// /// Boolean read. protected bool ReadBoolean() { return ReadByte() != 0; } /// /// Reads next uint16 data type from internal buffer. /// /// uint16 read protected ushort ReadUInt16() { var data = ReadBytes(2); return (ushort)(data[0] << 8 | data[1]); } /// /// Reads next uint32 data type from internal buffer. /// /// uint32 read protected uint ReadUInt32() { var data = 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 ulong ReadUInt64() { var data = ReadBytes(8); 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]); } /// /// Reads next int64 data type from internal buffer. /// /// int64 read protected long ReadInt64() { var data = 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]); } #if !TUNING /// /// Reads next string data type from internal buffer. /// /// string read protected string ReadAsciiString() { var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return Ascii.GetString(ReadBytes((int)length), 0, (int)length); } #endif /// /// Reads next string data type from internal buffer. /// /// string read protected string ReadString() { return ReadString(Utf8); } /// /// Reads next string data type from internal buffer. /// /// string read protected string ReadString(Encoding encoding) { var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return encoding.GetString(ReadBytes((int)length), 0, (int)length); } #if TUNING /// /// Reads next data type as byte array from internal buffer. /// /// /// The bytes read. /// protected byte[] ReadBinary() { var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Data longer than {0} is not supported.", int.MaxValue)); } return ReadBytes((int) length); } #else /// /// Reads next string data type from internal buffer. /// /// string read protected byte[] ReadBinaryString() { var length = ReadUInt32(); if (length > int.MaxValue) { throw new NotSupportedException(string.Format(CultureInfo.CurrentCulture, "Strings longer than {0} is not supported.", int.MaxValue)); } return ReadBytes((int)length); } #endif /// /// Reads next mpint data type from internal buffer. /// /// mpint read. protected BigInteger ReadBigInt() { var length = ReadUInt32(); var data = ReadBytes((int)length); #if TUNING return new BigInteger(data.Reverse()); #else return new BigInteger(data.Reverse().ToArray()); #endif } /// /// Reads next name-list data type from internal buffer. /// /// String array or read data.. protected string[] ReadNamesList() { var namesList = ReadString(); return namesList.Split(','); } /// /// Reads next extension-pair data type from internal buffer. /// /// Extensions pair dictionary. protected IDictionary ReadExtensionPair() { var result = new Dictionary(); while (!IsEndOfData) { var extensionName = ReadString(); var extensionData = ReadString(); result.Add(extensionName, extensionData); } return result; } #if TUNING /// /// Writes bytes array data into internal buffer. /// /// Byte array data to write. /// is null. protected void Write(byte[] data) { _stream.Write(data, 0, data.Length); } #else /// /// Writes bytes array data into internal buffer. /// /// Byte array data to write. /// is null. protected void Write(IEnumerable data) { _data.AddRange(data); } #endif #if TUNING /// /// Writes a sequence of bytes to the current SSH data stream and advances the current position /// within this stream by the number of bytes written. /// /// An array of bytes. This method write bytes from buffer to the current SSH data stream. /// The zero-based offset in at which to begin writing bytes to the SSH data stream. /// The number of bytes to be written to the current SSH data stream. /// is null. /// The sum of and is greater than the buffer length. /// or is negative. protected void Write(byte[] buffer, int offset, int count) { _stream.Write(buffer, offset, count); } #endif /// /// Writes byte data into internal buffer. /// /// Byte data to write. protected void Write(byte data) { #if TUNING _stream.WriteByte(data); #else _data.Add(data); #endif } /// /// Writes boolean data into internal buffer. /// /// Boolean data to write. protected void Write(bool data) { Write(data ? (byte) 1 : (byte) 0); } /// /// Writes uint16 data into internal buffer. /// /// uint16 data to write. protected void Write(ushort data) { Write(data.GetBytes()); } /// /// Writes uint32 data into internal buffer. /// /// uint32 data to write. protected void Write(uint data) { Write(data.GetBytes()); } /// /// Writes uint64 data into internal buffer. /// /// uint64 data to write. protected void Write(ulong data) { Write(data.GetBytes()); } /// /// Writes int64 data into internal buffer. /// /// int64 data to write. protected void Write(long data) { Write(data.GetBytes()); } /// /// Writes string data into internal buffer as ASCII. /// /// string data to write. protected void WriteAscii(string data) { Write(data, Ascii); } /// /// Writes string data into internal buffer using default encoding. /// /// string data to write. /// is null. protected void Write(string data) { Write(data, Utf8); } /// /// Writes string data into internal buffer using the specified encoding. /// /// string data to write. /// The character encoding to use. /// is null. /// is null. protected void Write(string data, Encoding encoding) { if (data == null) throw new ArgumentNullException("data"); if (encoding == null) throw new ArgumentNullException("encoding"); var bytes = encoding.GetBytes(data); #if TUNING var bytesLength = bytes.Length; Write((uint) bytesLength); Write(bytes, 0, bytesLength); #else Write((uint)bytes.Length); Write(bytes); #endif } #if TUNING /// /// Writes data into internal buffer. /// /// The data to write. /// is null. protected void WriteBinaryString(byte[] buffer) { if (buffer == null) throw new ArgumentNullException("buffer"); var bufferLength = buffer.Length; Write((uint)bufferLength); Write(buffer, 0, bufferLength); } /// /// Writes data into internal buffer. /// /// An array of bytes. This method write bytes from buffer to the current SSH data stream. /// The zero-based byte offset in at which to begin writing bytes to the SSH data stream. /// The number of bytes to be written to the current SSH data stream. /// is null. /// The sum of and is greater than the buffer length. /// or is negative. protected void WriteBinary(byte[] buffer, int offset, int count) { if (buffer == null) throw new ArgumentNullException("buffer"); Write((uint) count); Write(buffer, offset, count); } #else /// /// Writes string data into internal buffer. /// /// string data to write. /// is null. protected void WriteBinaryString(byte[] data) { if (data == null) throw new ArgumentNullException("data"); Write((uint)data.Length); _data.AddRange(data); } #endif /// /// Writes mpint data into internal buffer. /// /// mpint data to write. protected void Write(BigInteger data) { #if TUNING var bytes = data.ToByteArray().Reverse(); var bytesLength = bytes.Length; Write((uint) bytesLength); Write(bytes, 0, bytesLength); #else var bytes = data.ToByteArray().Reverse().ToList(); Write((uint)bytes.Count); Write(bytes); #endif } /// /// Writes name-list data into internal buffer. /// /// name-list data to write. protected void Write(string[] data) { 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) { WriteAscii(item.Key); WriteAscii(item.Value); } } } }