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; /// /// maxSliceLen taken from https://github.com/neo-project/neo/blob/38218bbee5bbe8b33cd8f9453465a19381c9a547/src/Neo/IO/Helper.cs#L77 /// 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[] slice, Func 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(byte[] buf, int offset, T[] slice, Func 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"); } } }