286 lines
7.6 KiB
C#
286 lines
7.6 KiB
C#
using System;
|
|
|
|
namespace FrostFS.SDK.Client;
|
|
|
|
internal static class RuleSerializer
|
|
{
|
|
const byte Version = 0; // increase if breaking change
|
|
|
|
const int ByteSize = 1;
|
|
const int UInt8Size = ByteSize;
|
|
const int BoolSize = ByteSize;
|
|
|
|
const long NullSlice = -1;
|
|
const int NullSliceSize = 1;
|
|
|
|
const byte ByteTrue = 1;
|
|
const byte ByteFalse = 0;
|
|
|
|
/// <summary>
|
|
/// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77
|
|
/// </summary>
|
|
const int MaxSliceLen = 0x1000000;
|
|
|
|
const int ChainMarshalVersion = 0;
|
|
|
|
internal static byte[] Serialize(FrostFsChain chain)
|
|
{
|
|
int s = UInt8Size // Marshaller version
|
|
+ UInt8Size // Chain version
|
|
+ SliceSize(chain.ID, b => ByteSize)
|
|
+ SliceSize(chain.Rules, RuleSize)
|
|
+ UInt8Size; // MatchType
|
|
|
|
byte[] buf = new byte[s];
|
|
|
|
int offset = UInt8Marshal(buf, 0, Version);
|
|
offset = UInt8Marshal(buf, offset, ChainMarshalVersion);
|
|
offset = SliceMarshal(buf, offset, chain.ID, ByteMarshal);
|
|
offset = SliceMarshal(buf, offset, chain.Rules, MarshalRule);
|
|
offset = UInt8Marshal(buf, offset, (byte)chain.MatchType);
|
|
|
|
VerifyMarshal(buf, offset);
|
|
|
|
return buf;
|
|
}
|
|
|
|
private static int Int64Size(long value)
|
|
{
|
|
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=92;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
|
|
// and
|
|
// https://cs.opensource.google/go/go/+/master:src/encoding/binary/varint.go;l=41;drc=dac9b9ddbd5160c5f4552410f5f8281bd5eed38c
|
|
ulong ux = (ulong)value << 1;
|
|
if (value < 0)
|
|
{
|
|
ux = ~ux;
|
|
}
|
|
|
|
int size = 0;
|
|
while (ux >= 0x80)
|
|
{
|
|
size++;
|
|
ux >>= 7;
|
|
}
|
|
|
|
return size + 1;
|
|
}
|
|
|
|
private static int SliceSize<T>(T[] slice, Func<T, int> sizeOf)
|
|
{
|
|
if (slice == null)
|
|
{
|
|
return NullSliceSize;
|
|
}
|
|
|
|
// Assuming Int64Size is the size of the slice
|
|
var size = Int64Size(slice.Length);
|
|
foreach (var v in slice)
|
|
{
|
|
size += sizeOf(v);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
private static int StringSize(string? s)
|
|
{
|
|
var len = s !=null ? s.Length : 0;
|
|
return Int64Size(len) + len;
|
|
}
|
|
|
|
private static int ActionsSize(Actions action)
|
|
{
|
|
return BoolSize // Inverted
|
|
+ SliceSize(action.Names, StringSize);
|
|
}
|
|
|
|
private static int ResourcesSize(Resource resource)
|
|
{
|
|
return BoolSize // Inverted
|
|
+ SliceSize(resource.Names, StringSize);
|
|
}
|
|
|
|
private static int ConditionSize(Condition condition)
|
|
{
|
|
return ByteSize // Op
|
|
+ ByteSize // Object
|
|
+ StringSize(condition.Key)
|
|
+ StringSize(condition.Value);
|
|
}
|
|
|
|
public static int RuleSize(FrostFsRule rule)
|
|
{
|
|
if (rule is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(rule));
|
|
}
|
|
|
|
return ByteSize // Status
|
|
+ ActionsSize(rule.Actions)
|
|
+ ResourcesSize(rule.Resources)
|
|
+ BoolSize // Any
|
|
+ SliceSize(rule.Conditions!, ConditionSize);
|
|
}
|
|
|
|
public static int UInt8Marshal(byte[] buf, int offset, byte value)
|
|
{
|
|
if (buf.Length - offset < 1)
|
|
{
|
|
throw new FrostFsException("Not enough bytes left to serialize value of type byte");
|
|
}
|
|
|
|
buf[offset] = value;
|
|
|
|
return offset + 1;
|
|
}
|
|
|
|
public static int ByteMarshal(byte[] buf, int offset, byte value)
|
|
{
|
|
return UInt8Marshal(buf, offset, value);
|
|
}
|
|
|
|
// PutVarint encodes an int64 into buf and returns the number of bytes written.
|
|
// If the buffer is too small, PutVarint will panic.
|
|
private static int PutVarint(byte[] buf, int offset, long x)
|
|
{
|
|
var ux = (ulong)x << 1;
|
|
|
|
if (x < 0)
|
|
{
|
|
ux = ~ux;
|
|
}
|
|
|
|
return PutUvarint(buf, offset, ux);
|
|
}
|
|
|
|
private static int PutUvarint(byte[] buf, int offset, ulong x)
|
|
{
|
|
while (x >= 0x80)
|
|
{
|
|
buf[offset] = (byte)(x | 0x80);
|
|
x >>= 7;
|
|
offset++;
|
|
}
|
|
|
|
buf[offset] = (byte)x;
|
|
|
|
return offset + 1;
|
|
}
|
|
|
|
public static int Int64Marshal(byte[] buf, int offset, long v)
|
|
{
|
|
if (buf.Length - offset < Int64Size(v))
|
|
{
|
|
throw new FrostFsException("Not enough bytes left to serialize value of type long");
|
|
}
|
|
|
|
return PutVarint(buf, offset, v);
|
|
}
|
|
|
|
public static int SliceMarshal<T>(byte[] buf, int offset, T[] slice, Func<byte[], int, T, int> marshalT)
|
|
{
|
|
if (slice == null)
|
|
{
|
|
return Int64Marshal(buf, offset, NullSlice);
|
|
}
|
|
|
|
if (slice.Length > MaxSliceLen)
|
|
{
|
|
throw new FrostFsException($"slice size if too big: {slice.Length}");
|
|
}
|
|
|
|
offset = Int64Marshal(buf, offset, slice.Length);
|
|
|
|
foreach (var v in slice)
|
|
{
|
|
offset = marshalT(buf, offset, v);
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
private static int BoolMarshal(byte[] buf, int offset, bool value)
|
|
{
|
|
return UInt8Marshal(buf, offset, value ? ByteTrue : ByteFalse);
|
|
}
|
|
|
|
private static int StringMarshal(byte[] buf, int offset, string value)
|
|
{
|
|
if (value == null)
|
|
{
|
|
throw new FrostFsException($"string value is null");
|
|
}
|
|
|
|
if (value.Length > MaxSliceLen)
|
|
{
|
|
throw new FrostFsException($"string is too long: {value.Length}");
|
|
}
|
|
|
|
if (buf.Length - offset < Int64Size(value.Length) + value.Length)
|
|
{
|
|
throw new FrostFsException($"Not enough bytes left to serialize value of type string with length {value.Length}");
|
|
}
|
|
|
|
offset = Int64Marshal(buf, offset, value.Length);
|
|
|
|
if (string.IsNullOrEmpty(value))
|
|
{
|
|
return offset;
|
|
}
|
|
|
|
Buffer.BlockCopy(System.Text.Encoding.UTF8.GetBytes(value), 0, buf, offset, value.Length);
|
|
|
|
return offset + value.Length;
|
|
}
|
|
|
|
private static int MarshalActions(byte[] buf, int offset, Actions action)
|
|
{
|
|
offset = BoolMarshal(buf, offset, action.Inverted);
|
|
|
|
return SliceMarshal(buf, offset, action.Names, StringMarshal);
|
|
}
|
|
|
|
private static int MarshalCondition(byte[] buf, int offset, Condition condition)
|
|
{
|
|
offset = ByteMarshal(buf, offset, (byte)condition.Op);
|
|
|
|
offset = ByteMarshal(buf, offset, (byte)condition.Kind);
|
|
|
|
offset = StringMarshal(buf, offset, condition.Key!);
|
|
|
|
return StringMarshal(buf, offset, condition.Value!);
|
|
}
|
|
|
|
private static int MarshalRule(byte[] buf, int offset, FrostFsRule rule)
|
|
{
|
|
if (rule is null)
|
|
{
|
|
throw new ArgumentNullException(nameof(rule));
|
|
}
|
|
|
|
offset = ByteMarshal(buf, offset, (byte)rule.Status);
|
|
|
|
offset = MarshalActions(buf, offset, rule.Actions);
|
|
|
|
offset = MarshalResources(buf, offset, rule.Resources);
|
|
|
|
offset = BoolMarshal(buf, offset, rule.Any);
|
|
|
|
return SliceMarshal(buf, offset, rule.Conditions!, MarshalCondition);
|
|
}
|
|
|
|
private static int MarshalResources(byte[] buf, int offset, Resource resources)
|
|
{
|
|
offset = BoolMarshal(buf, offset, resources.Inverted);
|
|
|
|
return SliceMarshal(buf, offset, resources.Names, StringMarshal);
|
|
}
|
|
|
|
private static void VerifyMarshal(byte[] buf, int lastOffset)
|
|
{
|
|
if (buf.Length != lastOffset)
|
|
{
|
|
throw new FrostFsException("actual data size differs from expected");
|
|
}
|
|
}
|
|
}
|