Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.AspNet/CompactBinaryFormatter.cs
2025-05-18 13:04:45 +08:00

165 lines
4.4 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.IO;
using System.Threading.Tasks;
using EpicGames.Core;
using EpicGames.Serialization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
namespace EpicGames.AspNet
{
/// <summary>
/// Global constants
/// </summary>
public static partial class CustomMediaTypeNames
{
/// <summary>
/// Media type for compact binary
/// </summary>
public const string UnrealCompactBinary = "application/x-ue-cb";
/// <summary>
/// Media type for compressed buffers
/// </summary>
public const string UnrealCompressedBuffer = "application/x-ue-comp";
/// <summary>
///
/// </summary>
public const string JupiterInlinedPayload = "application/x-jupiter-inline";
/// <summary>
/// Media type for compact binary packages
/// </summary>
public const string UnrealCompactBinaryPackage = "application/x-ue-cbpkg";
}
/// <summary>
/// Converter to allow reading compact binary objects as request bodies
/// </summary>
public class CbInputFormatter : InputFormatter
{
/// <summary>
/// Constructor
/// </summary>
public CbInputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(CustomMediaTypeNames.UnrealCompactBinary));
}
/// <inheritdoc/>
protected override bool CanReadType(Type type)
{
return true;
}
/// <inheritdoc/>
public override async Task<InputFormatterResult> ReadRequestBodyAsync(InputFormatterContext context)
{
// Buffer the data into an array
byte[] data;
try
{
using MemoryStream stream = new MemoryStream();
await context.HttpContext.Request.Body.CopyToAsync(stream);
data = stream.ToArray();
}
catch (BadHttpRequestException e)
{
ClientSendSlowExceptionUtil.MaybeThrowSlowSendException(e);
throw;
}
// Serialize the object
CbField field;
try
{
field = new CbField(data);
}
catch (Exception ex)
{
ILogger<CbInputFormatter> logger = context.HttpContext.RequestServices.GetRequiredService<ILogger<CbInputFormatter>>();
logger.LogError(ex, "Unable to parse compact binary: {Dump}", FormatHexDump(data, 256));
foreach ((string name, StringValues values) in context.HttpContext.Request.Headers)
{
foreach (string? value in values)
{
logger.LogInformation("Header {Name}: {Value}", name, value);
}
}
throw new Exception($"Unable to parse compact binary request: {FormatHexDump(data, 256)}", ex);
}
return await InputFormatterResult.SuccessAsync(CbSerializer.Deserialize(new CbField(data), context.ModelType)!);
}
static string FormatHexDump(byte[] data, int maxLength)
{
ReadOnlySpan<byte> span = data.AsSpan(0, Math.Min(data.Length, maxLength));
string hexString = StringUtils.FormatHexString(span);
char[] hexDump = new char[span.Length * 3];
for (int idx = 0; idx < span.Length; idx++)
{
hexDump[(idx * 3) + 0] = ((idx & 15) == 0) ? '\n' : ' ';
hexDump[(idx * 3) + 1] = hexString[(idx * 2) + 0];
hexDump[(idx * 3) + 2] = hexString[(idx * 2) + 1];
}
return new string(hexDump);
}
}
/// <summary>
/// Converter to allow writing compact binary objects as responses
/// </summary>
public class CbOutputFormatter : OutputFormatter
{
/// <summary>
/// Constructor
/// </summary>
public CbOutputFormatter()
{
SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse(CustomMediaTypeNames.UnrealCompactBinary));
}
/// <inheritdoc/>
protected override bool CanWriteType(Type? type)
{
return true;
}
/// <inheritdoc/>
public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context)
{
ReadOnlyMemory<byte> data;
if (context.Object is CbObject obj)
{
data = obj.GetView();
}
else
{
data = CbSerializer.Serialize(context.ObjectType!, context.Object!).GetView();
}
await context.HttpContext.Response.BodyWriter.WriteAsync(data);
}
}
/// <summary>
/// Special version of <see cref="CbOutputFormatter"/> which returns native CbObject encoded data. Can be
/// inserted at a high priority in the output formatter list to prevent transcoding to json.
/// </summary>
public class CbPreferredOutputFormatter : CbOutputFormatter
{
/// <inheritdoc/>
protected override bool CanWriteType(Type? type)
{
return type == typeof(CbObject);
}
}
}