1
0
Fork 0
forked from aly/qstbak
qstbak/Questionable.Model/Questionable.Model.IO/PathBundle.cs
2026-08-17 20:29:32 +10:00

111 lines
3.8 KiB
C#

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<KeyValuePair<string, byte[]>> 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<string, byte[]> 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<KeyValuePair<string, T>> Deserialize<T>(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<byte>)binaryReader.ReadBytes(Magic.Length).AsSpan()).SequenceEqual((ReadOnlySpan<byte>)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<string, T>[] result = new KeyValuePair<string, T>[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<T>(new ReadOnlySpan<byte>(container, item3, item4));
if (val == null)
{
throw new InvalidDataException("null payload for id " + item2);
}
T value = val;
result[num3] = new KeyValuePair<string, T>(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();
}
}