// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; namespace EpicGames.Core { /// /// Wraps a sequence of bytes that can be manipulated with value-like semantics /// [JsonSchemaString] [JsonConverter(typeof(ByteStringJsonConverter))] [TypeConverter(typeof(ByteStringTypeConverter))] public readonly struct ByteString : IEquatable, IComparable { /// /// Underlying data for this string /// public ReadOnlyMemory Data { get; } /// /// Accessor for the data underlying this string /// public ReadOnlySpan Span => Data.Span; /// /// Constructor /// /// public ByteString(ReadOnlyMemory data) => Data = data; /// /// Parses a byte string from a hexadecimal string /// /// Text to parse /// New byte string instance public static ByteString Parse(ReadOnlySpan text) => new ByteString(StringUtils.ParseHexString(text)); /// /// Parses a byte string from a hexadecimal string /// /// Text to parse /// New byte string instance public static ByteString Parse(Utf8String text) => Parse(text.Span); /// /// Parses a byte string from a hexadecimal string /// /// Text to parse /// New byte string instance public static ByteString Parse(string text) => new ByteString(StringUtils.ParseHexString(text)); /// /// Attempts to parse a hexadecimal string as a byte string /// /// Text to parse /// On success, receives the parsed string /// public static bool TryParse(ReadOnlySpan text, out ByteString byteString) { byte[]? bytes; if (StringUtils.TryParseHexString(text, out bytes)) { byteString = new ByteString(bytes); return true; } else { byteString = default; return false; } } /// /// Attempts to parse a hexadecimal string as a byte string /// /// Text to parse /// On success, receives the parsed string /// public static bool TryParse(Utf8String text, out ByteString byteString) => TryParse(text.Span, out byteString); /// /// Attempts to parse a hexadecimal string as a byte string /// /// Text to parse /// On success, receives the parsed string /// public static bool TryParse(string text, out ByteString byteString) { byte[]? bytes; if (StringUtils.TryParseHexString(text, out bytes)) { byteString = new ByteString(bytes); return true; } else { byteString = default; return false; } } /// public int CompareTo(ByteString other) => Data.Span.SequenceCompareTo(other.Data.Span); /// public override bool Equals([NotNullWhen(true)] object? obj) => obj is ByteString byteString && Equals(byteString); /// public bool Equals(ByteString other) => Data.Span.SequenceEqual(other.Data.Span); /// public override int GetHashCode() { HashCode hashCode = new HashCode(); hashCode.AddBytes(Data.Span); return hashCode.ToHashCode(); } /// public override string ToString() => StringUtils.FormatHexString(Data.Span); /// /// Creates a Utf8 string representing the bytes in this string /// public Utf8String ToUtf8String() => StringUtils.FormatUtf8HexString(Data.Span); #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public static bool operator ==(ByteString left, ByteString right) => left.Equals(right); public static bool operator !=(ByteString left, ByteString right) => !(left == right); public static bool operator <(ByteString left, ByteString right) => left.CompareTo(right) < 0; public static bool operator <=(ByteString left, ByteString right) => left.CompareTo(right) <= 0; public static bool operator >(ByteString left, ByteString right) => left.CompareTo(right) > 0; public static bool operator >=(ByteString left, ByteString right) => left.CompareTo(right) >= 0; #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } /// /// Type converter for ClusterId to and from JSON /// sealed class ByteStringJsonConverter : JsonConverter { /// public override ByteString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) => ByteString.Parse(reader.GetUtf8String()); /// public override void Write(Utf8JsonWriter writer, ByteString value, JsonSerializerOptions options) => writer.WriteStringValue(value.ToUtf8String().Span); } /// /// Type converter from strings to ClusterId objects /// sealed class ByteStringTypeConverter : TypeConverter { /// public override bool CanConvertFrom(ITypeDescriptorContext? context, Type? sourceType) { return sourceType == typeof(byte[]) || sourceType == typeof(Memory) || sourceType == typeof(ReadOnlyMemory) || sourceType == typeof(string) || sourceType == typeof(Utf8String); } /// public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object? value) { switch (value) { case null: return null; case byte[] bytes: return new ByteString(bytes); case Memory bytes: return new ByteString(bytes); case ReadOnlyMemory bytes: return new ByteString(bytes); case string stringValue: return ByteString.Parse(stringValue); case Utf8String stringValue: return ByteString.Parse(stringValue); default: throw new InvalidOperationException(); } } } }