using System;
using System.IO;
using System.IO.Pipelines;
using System.Text;
namespace BencodeNET.Objects
{
///
/// Represents a bencoded string, i.e. a byte-string.
/// It isn't necessarily human-readable.
///
///
/// The underlying value is a array.
///
public sealed class BString : BObject>, IComparable
{
///
/// The maximum number of digits that can be handled as the length part of a bencoded string.
///
internal const int LengthMaxDigits = 10;
///
/// The underlying bytes of the string.
///
public override ReadOnlyMemory Value { get; }
///
/// Gets the length of the string in bytes.
///
public int Length => Value.Length;
private static readonly Encoding DefaultEncoding = Encoding.UTF8;
///
/// Gets or sets the encoding used as the default with ToString().
///
///
public Encoding Encoding
{
get => _encoding;
set => _encoding = value ?? DefaultEncoding;
}
private Encoding _encoding;
///
/// Creates an empty ('0:').
///
public BString()
: this((string)null)
{
}
///
/// Creates a from bytes with the specified encoding.
///
/// The bytes representing the data.
/// The encoding of the bytes. Defaults to .
public BString(byte[] bytes, Encoding encoding = null)
{
Value = bytes ?? throw new ArgumentNullException(nameof(bytes));
_encoding = encoding ?? DefaultEncoding;
}
///
/// Creates a using the specified encoding to convert the string to bytes.
///
/// The string.
/// The encoding used to convert the string to bytes.
///
public BString(string str, Encoding encoding = null)
{
_encoding = encoding ?? DefaultEncoding;
if (string.IsNullOrEmpty(str))
{
Value = Array.Empty();
}
else
{
var maxByteCount = _encoding.GetMaxByteCount(str.Length);
var span = new byte[maxByteCount].AsSpan();
var length = _encoding.GetBytes(str.AsSpan(), span);
Value = span.Slice(0, length).ToArray();
}
}
///
public override int GetSizeInBytes() => Value.Length + 1 + Value.Length.DigitCount();
///
protected override void EncodeObject(Stream stream)
{
stream.Write(Value.Length);
stream.Write(':');
stream.Write(Value.Span);
}
///
protected override void EncodeObject(PipeWriter writer)
{
// Init
var size = GetSizeInBytes();
var buffer = writer.GetSpan(size);
// Write length
var writtenBytes = Encoding.GetBytes(Value.Length.ToString().AsSpan(), buffer);
// Write ':'
buffer[writtenBytes] = (byte) ':';
// Write value
Value.Span.CopyTo(buffer.Slice(writtenBytes + 1));
// Commit
writer.Advance(size);
}
#pragma warning disable 1591
public static implicit operator BString(string value) => new BString(value);
public static bool operator ==(BString first, BString second)
{
return first?.Equals(second) ?? second is null;
}
public static bool operator !=(BString first, BString second) => !(first == second);
public override bool Equals(object other) => other is BString bstring && Value.Span.SequenceEqual(bstring.Value.Span);
public bool Equals(BString bstring) => bstring != null && Value.Span.SequenceEqual(bstring.Value.Span);
public override int GetHashCode()
{
var bytesToHash = Math.Min(Value.Length, 32);
long hashValue = 0;
for (var i = 0; i < bytesToHash; i++)
{
hashValue = (37 * hashValue + Value.Span[i]) % int.MaxValue;
}
return (int)hashValue;
}
public int CompareTo(BString other)
{
return Value.Span.SequenceCompareTo(other.Value.Span);
}
#pragma warning restore 1591
///
/// Converts the underlying bytes to a string representation using the current value of the property.
///
///
/// A that represents this instance.
///
public override string ToString()
{
return _encoding.GetString(Value.Span);
}
///
/// Converts the underlying bytes to a string representation using the specified encoding.
///
/// The encoding to use to convert the underlying byte array to a .
///
/// A that represents this instance.
///
public string ToString(Encoding encoding)
{
encoding ??= _encoding;
return encoding.GetString(Value.Span);
}
}
}