51 lines
2.0 KiB
C#
51 lines
2.0 KiB
C#
using System.Buffers.Binary;
|
|
|
|
namespace WebDrop.Core;
|
|
|
|
/// <summary>帧封装的 26 字节明文头部,见 protocol.md §4。负载由服务端原样保存,从不解析。</summary>
|
|
public readonly record struct Envelope(FrameFlags Flags, Id16 ItemId, uint FrameIndex, int PayloadLength)
|
|
{
|
|
public const int HeaderSize = 26;
|
|
public const byte Version = 1;
|
|
|
|
public int TotalLength => HeaderSize + PayloadLength;
|
|
|
|
/// <summary>头部格式合法、保留位为 0、长度与 payload_len 严格一致。</summary>
|
|
public static bool TryParse(ReadOnlySpan<byte> data, out Envelope envelope)
|
|
{
|
|
envelope = default;
|
|
if (data.Length < HeaderSize || data[0] != Version) return false;
|
|
|
|
byte flags = data[1];
|
|
if ((flags & 0xF0) != 0) return false;
|
|
|
|
uint payloadLen = BinaryPrimitives.ReadUInt32BigEndian(data[22..]);
|
|
if (payloadLen > int.MaxValue || data.Length != HeaderSize + (int)payloadLen) return false;
|
|
|
|
envelope = new Envelope(
|
|
(FrameFlags)flags,
|
|
Id16.FromBytes(data.Slice(2, Id16.Size)),
|
|
BinaryPrimitives.ReadUInt32BigEndian(data[18..]),
|
|
(int)payloadLen);
|
|
return true;
|
|
}
|
|
|
|
public bool IsFinal => (Flags & FrameFlags.Final) != 0;
|
|
public bool IsGated => (Flags & FrameFlags.Gated) != 0;
|
|
public bool IsSnapshot => (Flags & FrameFlags.Snapshot) != 0;
|
|
public bool IsEnc => (Flags & FrameFlags.Enc) != 0;
|
|
|
|
/// <summary>条目级标志:同一条目的所有帧必须一致。</summary>
|
|
public FrameFlags ItemFlags => Flags & (FrameFlags.Snapshot | FrameFlags.Gated);
|
|
}
|
|
|
|
public static class PayloadTiers
|
|
{
|
|
public const int TagSize = 16;
|
|
private static readonly int[] Tiers = [1024, 4096, 16384, 65536, 262144, 1048576];
|
|
|
|
/// <summary>enc 负载 = 档位明文 + 16 字节 GCM 标签,服务端据此强制 padding。</summary>
|
|
public static bool IsValidEncPayloadLength(int payloadLength) =>
|
|
payloadLength > TagSize && Array.IndexOf(Tiers, payloadLength - TagSize) >= 0;
|
|
}
|