Initial version
This commit is contained in:
commit
e5b6226a85
68 changed files with 7338 additions and 0 deletions
101
DataTransfer.Client/Services/Frames.cs
Normal file
101
DataTransfer.Client/Services/Frames.cs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
using System.Text;
|
||||
|
||||
namespace DataTransfer.Client.Services;
|
||||
|
||||
/// <summary>Application frames. They are encrypted as a whole, so the relay server cannot see their type or content.</summary>
|
||||
public abstract record Frame;
|
||||
|
||||
public sealed record TextFrame(string Text) : Frame;
|
||||
|
||||
public sealed record FileStartFrame(int FileId, string Name, long Size) : Frame;
|
||||
|
||||
public sealed record FileChunkFrame(int FileId, ReadOnlyMemory<byte> Data) : Frame;
|
||||
|
||||
public sealed record FileCancelFrame(int FileId) : Frame;
|
||||
|
||||
/// <summary>Plaintext layout: version (byte) | type (byte) | sequence number (int64) | type-specific body.</summary>
|
||||
internal static class FrameCodec
|
||||
{
|
||||
private const byte Version = 1;
|
||||
|
||||
private enum FrameType : byte
|
||||
{
|
||||
Text = 1,
|
||||
FileStart = 2,
|
||||
FileChunk = 3,
|
||||
FileCancel = 4
|
||||
}
|
||||
|
||||
public static byte[] Encode(long sequence, Frame frame)
|
||||
{
|
||||
var capacity = frame is FileChunkFrame chunk ? chunk.Data.Length + 32 : 256;
|
||||
using var stream = new MemoryStream(capacity);
|
||||
using (var writer = new BinaryWriter(stream, Encoding.UTF8, true))
|
||||
{
|
||||
writer.Write(Version);
|
||||
switch (frame)
|
||||
{
|
||||
case TextFrame text:
|
||||
WriteHeader(writer, FrameType.Text, sequence);
|
||||
writer.Write(text.Text);
|
||||
break;
|
||||
case FileStartFrame start:
|
||||
WriteHeader(writer, FrameType.FileStart, sequence);
|
||||
writer.Write(start.FileId);
|
||||
writer.Write(start.Name);
|
||||
writer.Write(start.Size);
|
||||
break;
|
||||
case FileChunkFrame fileChunk:
|
||||
WriteHeader(writer, FrameType.FileChunk, sequence);
|
||||
writer.Write(fileChunk.FileId);
|
||||
writer.Write(fileChunk.Data.Span);
|
||||
break;
|
||||
case FileCancelFrame cancel:
|
||||
WriteHeader(writer, FrameType.FileCancel, sequence);
|
||||
writer.Write(cancel.FileId);
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(frame), frame, "Unknown frame.");
|
||||
}
|
||||
}
|
||||
|
||||
return stream.ToArray();
|
||||
}
|
||||
|
||||
/// <exception cref="InvalidDataException">The plaintext is not a valid frame.</exception>
|
||||
public static (long Sequence, Frame Frame) Decode(byte[] plaintext)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var reader = new BinaryReader(new MemoryStream(plaintext, false), Encoding.UTF8);
|
||||
if (reader.ReadByte() != Version)
|
||||
{
|
||||
throw new InvalidDataException("Unsupported frame version.");
|
||||
}
|
||||
|
||||
var type = (FrameType)reader.ReadByte();
|
||||
var sequence = reader.ReadInt64();
|
||||
Frame frame = type switch
|
||||
{
|
||||
FrameType.Text => new TextFrame(reader.ReadString()),
|
||||
FrameType.FileStart => new FileStartFrame(reader.ReadInt32(), reader.ReadString(), reader.ReadInt64()),
|
||||
FrameType.FileChunk => new FileChunkFrame(reader.ReadInt32(), plaintext.AsMemory((int)reader.BaseStream.Position)),
|
||||
FrameType.FileCancel => new FileCancelFrame(reader.ReadInt32()),
|
||||
_ => throw new InvalidDataException("Unknown frame type.")
|
||||
};
|
||||
|
||||
return (sequence, frame);
|
||||
}
|
||||
catch (Exception ex) when (ex is IOException or FormatException)
|
||||
{
|
||||
// EndOfStreamException (truncated), IOException (negative string length), FormatException (bad 7-bit length).
|
||||
throw new InvalidDataException("Malformed frame.", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void WriteHeader(BinaryWriter writer, FrameType type, long sequence)
|
||||
{
|
||||
writer.Write((byte)type);
|
||||
writer.Write(sequence);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue