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; }