// 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();
}
}
}
}