elura-cli 0.2.6

Project scaffold generator for Elura applications
using System;
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 static byte[] Utf8(string text)
        {
            if (text == null)
                throw new ArgumentNullException(nameof(text));
            return System.Text.Encoding.UTF8.GetBytes(text);
        }

        public static string Text(ReadOnlySpan<byte> bytes)
        {
            return System.Text.Encoding.UTF8.GetString(bytes);
        }
    }

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

    public sealed class Elr2Frame
    {
        internal Elr2Frame(
            FrameKind kind,
            byte flags,
            uint route,
            ulong requestId,
            uint sequence,
            byte[] payload)
        {
            Kind = kind;
            Flags = flags;
            Route = route;
            RequestId = requestId;
            Sequence = sequence;
            Payload = payload ?? throw new ArgumentNullException(nameof(payload));
        }

        public FrameKind Kind { get; }
        public byte Flags { get; }
        public uint Route { get; }
        public ulong RequestId { get; }
        public uint Sequence { get; }
        public byte[] Payload { get; }

        public static Elr2Frame Request(
            uint route,
            ulong requestId,
            byte[]? payload = null,
            uint sequence = 0)
        {
            return Create(
                FrameKind.Request,
                route,
                requestId,
                sequence,
                payload ?? Array.Empty<byte>());
        }

        public static Elr2Frame Response(Elr2Frame request, byte[]? payload = null)
        {
            RequireRequest(request);
            return Create(
                FrameKind.Response,
                request.Route,
                request.RequestId,
                request.Sequence,
                payload ?? Array.Empty<byte>());
        }

        public static Elr2Frame Error(Elr2Frame request, byte[]? payload = null)
        {
            RequireRequest(request);
            return Create(
                FrameKind.Error,
                request.Route,
                request.RequestId,
                request.Sequence,
                payload ?? Array.Empty<byte>());
        }

        public static Elr2Frame Push(uint route, byte[]? payload = null, uint sequence = 0)
        {
            return Create(
                FrameKind.Push,
                route,
                0,
                sequence,
                payload ?? Array.Empty<byte>());
        }

        private static Elr2Frame Create(
            FrameKind kind,
            uint route,
            ulong requestId,
            uint sequence,
            byte[] payload)
        {
            var frame = new Elr2Frame(kind, 0, route, requestId, sequence, payload);
            Elr2Codec.Validate(frame, Elr2.AbsoluteMaxPayload);
            return frame;
        }

        private static void RequireRequest(Elr2Frame request)
        {
            if (request == null)
                throw new ArgumentNullException(nameof(request));
            if (request.Kind != FrameKind.Request)
                throw new Elr2ProtocolException("response source must be a request frame");
        }
    }

    public sealed class Elr2ProtocolException : Exception
    {
        public Elr2ProtocolException(string message)
            : base(message)
        {
        }

        public Elr2ProtocolException(string message, Exception innerException)
            : base(message, innerException)
        {
        }
    }

    public static class Elr2Codec
    {
        public static void Validate(Elr2Frame frame, int maxPayload = Elr2.DefaultMaxPayload)
        {
            ValidateLimit(maxPayload);
            if (frame == null)
                throw new ArgumentNullException(nameof(frame));
            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(typeof(FrameKind), 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.Slice(4), Elr2.Version);
            header[6] = (byte)frame.Kind;
            header[7] = frame.Flags;
            BinaryPrimitives.WriteUInt32BigEndian(header.Slice(8), frame.Route);
            BinaryPrimitives.WriteUInt64BigEndian(header.Slice(12), frame.RequestId);
            BinaryPrimitives.WriteUInt32BigEndian(header.Slice(20), frame.Sequence);
            BinaryPrimitives.WriteUInt32BigEndian(header.Slice(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.Slice(4)) != Elr2.Version)
                throw new Elr2ProtocolException("unsupported Elura version");

            var kind = (FrameKind)message[6];
            if (!Enum.IsDefined(typeof(FrameKind), kind))
                throw new Elr2ProtocolException("unknown frame kind");
            var payloadLength = BinaryPrimitives.ReadUInt32BigEndian(message.Slice(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.Slice(8)),
                BinaryPrimitives.ReadUInt64BigEndian(message.Slice(12)),
                BinaryPrimitives.ReadUInt32BigEndian(message.Slice(20)),
                message.Slice(Elr2.HeaderLength).ToArray());
            Validate(frame, maxPayload);
            return frame;
        }

        internal static void ValidateLimit(int maxPayload)
        {
            if (maxPayload <= 0 || maxPayload > 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.Slice(4)) != Elr2.Version)
                throw new Elr2ProtocolException("unsupported Elura version");
            var payloadLength = BinaryPrimitives.ReadUInt32BigEndian(header.Slice(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;
        }
    }
}