sources/Google.Solutions.Ssh/Format/SshWriter.cs (83 lines of code) (raw):
//
// Copyright 2022 Google LLC
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
using Google.Solutions.Common.Format;
using Google.Solutions.Common.Util;
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace Google.Solutions.Ssh.Format
{
/// <summary>
/// Writer for SSH-structured data, see RFC4251 section 5.
/// </summary>
internal class SshWriter : IDisposable
{
private readonly Stream stream;
public SshWriter(Stream stream)
{
Debug.Assert(stream.CanWrite);
this.stream = stream.ExpectNotNull(nameof(stream));
}
//---------------------------------------------------------------------
// Publics.
//---------------------------------------------------------------------
public void WriteByte(byte b)
{
//
// A byte represents an arbitrary 8 - bit value(octet).Fixed length
// data is sometimes represented as an array of bytes, written
// byte[n], where n is the number of bytes in the array.
//
this.stream.Write(new byte[] { b }, 0, 1);
}
public void WriteBoolean(bool b)
{
//
// A boolean value is stored as a single byte.The value 0
// represents FALSE, and the value 1 represents TRUE. All non-zero
// values MUST be interpreted as TRUE; however, applications MUST NOT
// store values other than 0 and 1.
//
this.stream.Write(new byte[] { b ? (byte)1 : (byte)0 }, 0, 1);
}
public void WriteUint32(uint i)
{
//
// Represents a 32 - bit unsigned integer. Stored as four bytes in the
// order of decreasing significance(network byte order).For
// example: the value 699921578(0x29b7f4aa) is stored as 29 b7 f4
// aa.
//
var bytes = new byte[4];
BigEndian.EncodeUInt32(i, bytes, 0);
this.stream.Write(bytes, 0, 4);
}
public void WriteUint64(ulong i)
{
//
// Represents a 64 - bit unsigned integer. Stored as eight bytes in
// the order of decreasing significance(network byte order).
//
var bytes = new byte[8];
BigEndian.EncodeUInt64(i, bytes, 0);
this.stream.Write(bytes, 0, 8);
}
public void WriteString(string s)
{
//
// Arbitrary length binary string.Strings are allowed to contain
// arbitrary binary data, including null characters and 8 - bit
// characters.They are stored as a uint32 containing its length
// (number of bytes that follow) and zero(= empty string) or more
// bytes that are the value of the string.Terminating null
// characters are not used.
//
// Strings are also used to store text.In that case, US-ASCII is
// used for internal names, and ISO-10646 UTF-8 for text that might
// be displayed to the user.The terminating null character SHOULD
//
// NOT normally be stored in the string. For example: the US-ASCII
//
// string "testing" is represented as 00 00 00 07 t e s t i n g.The
// UTF-8 mapping does not alter the encoding of US-ASCII characters.
//
WriteString(Encoding.ASCII.GetBytes(s));
}
public void WriteString(byte[] bytes)
{
//
// Write variable-length byte array as a string.
//
WriteUint32((uint)bytes.Length);
this.stream.Write(bytes, 0, bytes.Length);
}
public void WriteMultiPrecisionInteger(byte[] bigEndian)
{
//
// Represents multiple precision integers in two's complement format,
// stored as a string, 8 bits per byte, MSB first. Negative numbers
// have the value 1 as the most significant bit of the first byte of
// the data partition. If the most significant bit would be set for
// a positive number, the number MUST be preceded by a zero byte.
// Unnecessary leading bytes with the value 0 or 255 MUST NOT be
// included. The value zero MUST be stored as a string with zero
// bytes of data.
//
var startIndex = Array.FindIndex(bigEndian, b => b != 0);
if (startIndex < 0)
{
//
// All zeros.
//
WriteUint32((uint)0);
}
else
{
if ((bigEndian[startIndex] & 0x80) == 128)
{
//
// If the most significant bit would be set for
// a positive number, the number MUST be preceded
// by a zero byte.
//
WriteUint32((uint)(bigEndian.Length - startIndex + 1));
WriteByte((byte)0);
}
else
{
WriteUint32((uint)(bigEndian.Length - startIndex));
}
this.stream.Write(bigEndian, startIndex, bigEndian.Length - startIndex);
}
}
public void Flush() => this.stream.Flush();
//---------------------------------------------------------------------
// IDisposable.
//---------------------------------------------------------------------
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.stream.Flush();
}
this.stream.Dispose();
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}