using System.Buffers.Binary; using System.Security.Cryptography; using WebDrop.Core; using WebDrop.Storage; namespace WebDrop.Core.Tests; public sealed class FakeClock : IClock { public long NowMs { get; set; } = 1_700_000_000_000; public void Advance(TimeSpan span) => NowMs += (long)span.TotalMilliseconds; } public sealed class RecordingEvents : IChannelEvents { public List<(Id16 Channel, long Seq)> Advances { get; } = []; public int Settings, Seats, Deleted; public void Advanced(Id16 channelId, long seq) { lock (Advances) Advances.Add((channelId, seq)); } public void SettingsChanged(Id16 channelId) => Interlocked.Increment(ref Settings); public void SeatsChanged(Id16 channelId) => Interlocked.Increment(ref Seats); public void ChannelDeleted(Id16 channelId) => Interlocked.Increment(ref Deleted); } /// 一个频道的全部凭据,模拟客户端从 K / A 派生出的令牌。 public sealed class TestChannel(Id16 id, ChannelMode mode, byte[] read, byte[] write, byte[] owner, byte[] ownerEndpoint) { public Id16 Id { get; } = id; public ChannelMode Mode { get; } = mode; public byte[] ReadToken { get; } = read; public byte[] WriteToken { get; } = write; public byte[] OwnerToken { get; } = owner; public Credentials Owner(byte[] endpoint) => new(Role.Owner, OwnerToken, endpoint, "owner"); public Credentials Member(byte[] endpoint) => new(Role.Write, WriteToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null); public Credentials Reader(byte[] endpoint) => new(Role.Read, ReadToken, endpoint, endpoint.AsSpan().SequenceEqual(ownerEndpoint) ? "owner" : null); } public sealed class TestEnv : IDisposable { public string Dir { get; } = Path.Combine(Path.GetTempPath(), "webdrop-tests-" + Guid.NewGuid().ToString("N")); public FakeClock Clock { get; } = new(); public RecordingEvents Events { get; } = new(); public ServerLimits Limits { get; } = new(); public SqliteChannelStore Store { get; } public DiskBlobStore Blobs { get; } public ChannelService Service { get; private set; } public TestEnv() { Directory.CreateDirectory(Dir); Store = new SqliteChannelStore(Path.Combine(Dir, "test.db")); Blobs = new DiskBlobStore(Path.Combine(Dir, "blobs")); Service = new ChannelService(Store, Blobs, Clock, Limits, Events); } public static byte[] Random(int n) => RandomNumberGenerator.GetBytes(n); public static byte[] Endpoint() => TokenHash.Compute(Random(32)); public static Id16 NewId() => Id16.FromBytes(Random(16)); public TestChannel Create( byte[] ownerEndpoint, ChannelMode mode = ChannelMode.Enc, ChannelSettings? settings = null, bool withWriteHash = true) { var id = NewId(); var read = Random(32); var write = Random(32); var owner = Random(32); settings ??= new ChannelSettings(); settings.Name ??= "Test space"; Service.CreateChannel(new CreateChannelRequest { Id = id.ToBase64Url(), Mode = mode, Settings = settings, ReadHash = mode == ChannelMode.Enc ? Codec.Encode(TokenHash.Compute(read)) : null, WriteHash = withWriteHash ? Codec.Encode(TokenHash.Compute(write)) : null, OwnerHash = Codec.Encode(TokenHash.Compute(owner)), }, ownerEndpoint, "owner"); return new TestChannel(id, mode, read, write, owner, ownerEndpoint); } /// 构造一个封装。enc 模式的负载长度必须是 档位+16。 public static byte[] Frame(Id16 item, uint index, FrameFlags flags, int payloadLength, byte fill = 0x42) { var bytes = new byte[Envelope.HeaderSize + payloadLength]; bytes[0] = Envelope.Version; bytes[1] = (byte)flags; item.WriteTo(bytes.AsSpan(2)); BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(18), index); BinaryPrimitives.WriteUInt32BigEndian(bytes.AsSpan(22), (uint)payloadLength); bytes.AsSpan(Envelope.HeaderSize).Fill(fill); return bytes; } public const int EncPayload = 1024 + 16; public const FrameFlags SmallEnc = FrameFlags.Final | FrameFlags.Enc; public void Dispose() { Store.Dispose(); try { Directory.Delete(Dir, recursive: true); } catch (IOException) { } } }