108 lines
4.3 KiB
C#
108 lines
4.3 KiB
C#
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);
|
|
}
|
|
|
|
/// <summary>一个频道的全部凭据,模拟客户端从 K / A 派生出的令牌。</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>构造一个封装。enc 模式的负载长度必须是 档位+16。</summary>
|
|
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) { }
|
|
}
|
|
}
|