elura-cli 0.2.4

Project scaffold generator for Elura applications
using System.Buffers.Binary;

namespace Elura.Protocol;

public static class Elr2
{
    public const uint Magic = 0x454c5232;
    public const ushort Version = {{ELR2_VERSION}};
    public const int HeaderLength = 28;
    public const int DefaultMaxPayload = 1 << 20;
    public const int AbsoluteMaxPayload = 64 << 20;
    public const string ProtocolIdentifier = "{{PROTOCOL_IDENTIFIER}}";
}

public enum FrameKind : byte
{
    Request = 1,
    Response = 2,
    Push = 3,
    Error = 4,
}

public sealed record Elr2Frame(
    FrameKind Kind,
    byte Flags,
    uint Route,
    ulong RequestId,
    uint Sequence,
    byte[] Payload);

public sealed class Elr2ProtocolException(string message) : Exception(message);

public static class Elr2Codec
{
    public static void Validate(Elr2Frame frame, int maxPayload = Elr2.DefaultMaxPayload)
    {
        ValidateLimit(maxPayload);
        ArgumentNullException.ThrowIfNull(frame);
        ArgumentNullException.ThrowIfNull(frame.Payload);
        if (frame.Payload.Length > maxPayload)
            throw new Elr2ProtocolException("payload is too large");
        if (frame.Flags != 0)
            throw new Elr2ProtocolException("unsupported frame flags");
        if (frame.Route == 0)
            throw new Elr2ProtocolException("route must be non-zero");
        if (!Enum.IsDefined(frame.Kind))
            throw new Elr2ProtocolException("unknown frame kind");
        if (frame.Kind == FrameKind.Push)
        {
            if (frame.RequestId != 0)
                throw new Elr2ProtocolException("push request id must be zero");
        }
        else if (frame.RequestId == 0)
        {
            throw new Elr2ProtocolException("request id must be non-zero");
        }
    }

    public static byte[] Encode(Elr2Frame frame, int maxPayload = Elr2.DefaultMaxPayload)
    {
        Validate(frame, maxPayload);
        var output = new byte[checked(Elr2.HeaderLength + frame.Payload.Length)];
        var header = output.AsSpan(0, Elr2.HeaderLength);
        BinaryPrimitives.WriteUInt32BigEndian(header, Elr2.Magic);
        BinaryPrimitives.WriteUInt16BigEndian(header[4..], Elr2.Version);
        header[6] = (byte)frame.Kind;
        header[7] = frame.Flags;
        BinaryPrimitives.WriteUInt32BigEndian(header[8..], frame.Route);
        BinaryPrimitives.WriteUInt64BigEndian(header[12..], frame.RequestId);
        BinaryPrimitives.WriteUInt32BigEndian(header[20..], frame.Sequence);
        BinaryPrimitives.WriteUInt32BigEndian(header[24..], (uint)frame.Payload.Length);
        frame.Payload.CopyTo(output, Elr2.HeaderLength);
        return output;
    }

    public static Elr2Frame Decode(ReadOnlySpan<byte> message, int maxPayload = Elr2.DefaultMaxPayload)
    {
        ValidateLimit(maxPayload);
        if (message.Length < Elr2.HeaderLength)
            throw new Elr2ProtocolException("incomplete Elura frame");
        if (BinaryPrimitives.ReadUInt32BigEndian(message) != Elr2.Magic)
            throw new Elr2ProtocolException("invalid Elura magic");
        if (BinaryPrimitives.ReadUInt16BigEndian(message[4..]) != Elr2.Version)
            throw new Elr2ProtocolException("unsupported Elura version");
        var kind = (FrameKind)message[6];
        if (!Enum.IsDefined(kind))
            throw new Elr2ProtocolException("unknown frame kind");
        var payloadLength = BinaryPrimitives.ReadUInt32BigEndian(message[24..]);
        if (payloadLength > maxPayload)
            throw new Elr2ProtocolException("Elura payload is too large");
        if (message.Length != checked(Elr2.HeaderLength + (int)payloadLength))
            throw new Elr2ProtocolException("Elura message must contain exactly one frame");
        var frame = new Elr2Frame(
            kind,
            message[7],
            BinaryPrimitives.ReadUInt32BigEndian(message[8..]),
            BinaryPrimitives.ReadUInt64BigEndian(message[12..]),
            BinaryPrimitives.ReadUInt32BigEndian(message[20..]),
            message[Elr2.HeaderLength..].ToArray());
        Validate(frame, maxPayload);
        return frame;
    }

    internal static void ValidateLimit(int maxPayload)
    {
        if (maxPayload is <= 0 or > Elr2.AbsoluteMaxPayload)
            throw new ArgumentOutOfRangeException(nameof(maxPayload), "max payload must be in 1..=64MiB");
    }
}

public sealed class Elr2StreamDecoder
{
    private readonly int _maxPayload;
    private byte[] _buffer = new byte[4096];
    private int _count;

    public Elr2StreamDecoder(int maxPayload = Elr2.DefaultMaxPayload)
    {
        Elr2Codec.ValidateLimit(maxPayload);
        _maxPayload = maxPayload;
    }

    public int BufferedBytes => _count;

    public void Append(ReadOnlySpan<byte> chunk)
    {
        var required = checked(_count + chunk.Length);
        if (required > _buffer.Length)
            Array.Resize(ref _buffer, Math.Max(required, checked(_buffer.Length * 2)));
        chunk.CopyTo(_buffer.AsSpan(_count));
        _count = required;
    }

    public bool TryRead(out Elr2Frame? frame)
    {
        frame = null;
        if (_count < Elr2.HeaderLength)
            return false;
        var header = _buffer.AsSpan(0, Elr2.HeaderLength);
        if (BinaryPrimitives.ReadUInt32BigEndian(header) != Elr2.Magic)
            throw new Elr2ProtocolException("invalid Elura magic");
        if (BinaryPrimitives.ReadUInt16BigEndian(header[4..]) != Elr2.Version)
            throw new Elr2ProtocolException("unsupported Elura version");
        var payloadLength = BinaryPrimitives.ReadUInt32BigEndian(header[24..]);
        if (payloadLength > _maxPayload)
            throw new Elr2ProtocolException("Elura payload is too large");
        var total = checked(Elr2.HeaderLength + (int)payloadLength);
        if (_count < total)
            return false;
        frame = Elr2Codec.Decode(_buffer.AsSpan(0, total), _maxPayload);
        Buffer.BlockCopy(_buffer, total, _buffer, 0, _count - total);
        _count -= total;
        return true;
    }
}