using System.Buffers.Binary;
namespace WebDrop.Core;
/// 帧封装的 26 字节明文头部,见 protocol.md §4。负载由服务端原样保存,从不解析。
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;
/// 头部格式合法、保留位为 0、长度与 payload_len 严格一致。
public static bool TryParse(ReadOnlySpan 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;
/// 条目级标志:同一条目的所有帧必须一致。
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];
/// enc 负载 = 档位明文 + 16 字节 GCM 标签,服务端据此强制 padding。
public static bool IsValidEncPayloadLength(int payloadLength) =>
payloadLength > TagSize && Array.IndexOf(Tiers, payloadLength - TagSize) >= 0;
}