feat(server): implement sealed share lifecycle
Signed-off-by: JustMe <mr.toing@outlook.com>
This commit is contained in:
1 parent
e02e024605
commit
42b93ae2d8
45 files changed
+6327
No files matched your search
@@ -0,0 +1,105 @@
|
||||
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)
|
||||
{
|
||||
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);
|
||||
public Credentials Member(byte[] endpoint) => new(Role.Write, WriteToken, endpoint);
|
||||
public Credentials Reader(byte[] endpoint) => new(Role.Read, ReadToken, endpoint);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
return new TestChannel(id, mode, read, write, owner);
|
||||
}
|
||||
|
||||
/// <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) { }
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user