using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Threading.Tasks; namespace Questionable.Model.IO; internal static class PathBundle { private const int NonceSize = 12; private const int TagSize = 16; private static readonly byte[] Magic = "QPB1"u8.ToArray(); internal static byte[] Encode(IReadOnlyList> entries, byte[] key, byte[] nonce) { using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(Magic); binaryWriter.Write(entries.Count); foreach (KeyValuePair entry in entries) { entry.Deconstruct(out var key2, out var value); string s = key2; byte[] array = value; byte[] bytes = Encoding.UTF8.GetBytes(s); binaryWriter.Write(checked((byte)bytes.Length)); binaryWriter.Write(bytes); binaryWriter.Write(array.Length); binaryWriter.Write(array); } } using MemoryStream memoryStream2 = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(memoryStream2, CompressionLevel.Optimal, leaveOpen: true)) { deflateStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length); } byte[] array2 = memoryStream2.ToArray(); byte[] array3 = new byte[28 + array2.Length]; nonce.CopyTo(array3, 0); using AesGcm aesGcm = new AesGcm(key, 16); aesGcm.Encrypt(nonce, array2, array3.AsSpan(28), array3.AsSpan(12, 16)); return array3; } internal static List> Deserialize(Stream stream, byte[] key) { byte[] container = Decode(stream, key); using MemoryStream memoryStream = new MemoryStream(container); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8); if (!((ReadOnlySpan)binaryReader.ReadBytes(Magic.Length).AsSpan()).SequenceEqual((ReadOnlySpan)Magic)) { throw new InvalidDataException("not a path bundle"); } int num = binaryReader.ReadInt32(); (string Id, int Offset, int Length)[] entries = new(string, int, int)[num]; for (int i = 0; i < num; i++) { int count = binaryReader.ReadByte(); string item = Encoding.UTF8.GetString(binaryReader.ReadBytes(count)); int num2 = binaryReader.ReadInt32(); entries[i] = (Id: item, Offset: (int)memoryStream.Position, Length: num2); memoryStream.Seek(num2, SeekOrigin.Current); } KeyValuePair[] result = new KeyValuePair[num]; Parallel.For(0, num, delegate(int num3) { (string Id, int Offset, int Length) tuple = entries[num3]; string item2 = tuple.Id; int item3 = tuple.Offset; int item4 = tuple.Length; T val = JsonSerializer.Deserialize(new ReadOnlySpan(container, item3, item4)); if (val == null) { throw new InvalidDataException("null payload for id " + item2); } T value = val; result[num3] = new KeyValuePair(item2, value); }); return result.ToList(); } private static byte[] Decode(Stream stream, byte[] key) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); byte[] array = memoryStream.ToArray(); if (array.Length < 28) { throw new InvalidDataException("bundle truncated"); } byte[] array2 = new byte[array.Length - 12 - 16]; using (AesGcm aesGcm = new AesGcm(key, 16)) { aesGcm.Decrypt(array.AsSpan(0, 12), array.AsSpan(28), array.AsSpan(12, 16), array2); } using MemoryStream memoryStream2 = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(new MemoryStream(array2), CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream2); } return memoryStream2.ToArray(); } }