241 lines
8.8 KiB
C#
241 lines
8.8 KiB
C#
using System;
|
|
using System.Drawing;
|
|
using System.Drawing.Imaging;
|
|
using System.IO;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using DCIT.Core.Utilities;
|
|
|
|
namespace DCIT.Core.Services
|
|
{
|
|
public class BmpDiffCodec
|
|
{
|
|
private const string Magic = "DCITBMP1";
|
|
private const ushort HeaderVersion = 1;
|
|
private const ushort Flags = 0;
|
|
private const int FixedWidth = 1024;
|
|
private const int HeaderSize = 8 + 2 + 2 + 8 + 32;
|
|
|
|
public void EncodeJson(string canonicalJson, string outputPath)
|
|
{
|
|
var payloadBytes = Encoding.UTF8.GetBytes(canonicalJson ?? string.Empty);
|
|
using (var stream = new MemoryStream(payloadBytes))
|
|
{
|
|
EncodeFromStream(stream, outputPath);
|
|
}
|
|
}
|
|
|
|
public void EncodeFromStream(Stream jsonStream, string outputPath)
|
|
{
|
|
if (jsonStream == null)
|
|
{
|
|
throw new ArgumentNullException("jsonStream");
|
|
}
|
|
|
|
byte[] hash;
|
|
long length;
|
|
|
|
if (jsonStream.CanSeek)
|
|
{
|
|
length = jsonStream.Length;
|
|
using (var sha = SHA256.Create())
|
|
{
|
|
hash = sha.ComputeHash(jsonStream);
|
|
}
|
|
|
|
jsonStream.Position = 0;
|
|
}
|
|
else
|
|
{
|
|
using (var buffered = new MemoryStream())
|
|
{
|
|
jsonStream.CopyTo(buffered);
|
|
length = buffered.Length;
|
|
using (var sha = SHA256.Create())
|
|
{
|
|
hash = sha.ComputeHash(buffered);
|
|
}
|
|
|
|
buffered.Position = 0;
|
|
WriteBitmapWithPayload(outputPath, length, hash, buffered);
|
|
return;
|
|
}
|
|
}
|
|
|
|
WriteBitmapWithPayload(outputPath, length, hash, jsonStream);
|
|
}
|
|
|
|
public string DecodeJson(string bmpPath)
|
|
{
|
|
using (var bitmap = new Bitmap(bmpPath))
|
|
{
|
|
var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
|
var bitmapData = bitmap.LockBits(rect, ImageLockMode.ReadOnly, bitmap.PixelFormat);
|
|
try
|
|
{
|
|
var rowLength = bitmap.Width;
|
|
var pixelBytes = new byte[rowLength * bitmap.Height];
|
|
for (var row = 0; row < bitmap.Height; row++)
|
|
{
|
|
var source = IntPtr.Add(bitmapData.Scan0, row * bitmapData.Stride);
|
|
Marshal.Copy(source, pixelBytes, row * rowLength, rowLength);
|
|
}
|
|
|
|
var payloadBytes = ParseBlob(pixelBytes);
|
|
return Encoding.UTF8.GetString(payloadBytes);
|
|
}
|
|
finally
|
|
{
|
|
bitmap.UnlockBits(bitmapData);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void WriteBitmapWithPayload(string outputPath, long payloadLength, byte[] payloadHash, Stream payloadStream)
|
|
{
|
|
var totalBytes = HeaderSize + payloadLength;
|
|
var height = Math.Max(1, (int)Math.Ceiling(totalBytes / (double)FixedWidth));
|
|
var pixelCount = FixedWidth * height;
|
|
var pixelBytes = new byte[pixelCount];
|
|
|
|
var offset = 0;
|
|
var magicBytes = Encoding.ASCII.GetBytes(Magic);
|
|
Buffer.BlockCopy(magicBytes, 0, pixelBytes, offset, magicBytes.Length);
|
|
offset += magicBytes.Length;
|
|
|
|
pixelBytes[offset++] = (byte)(HeaderVersion & 0xFF);
|
|
pixelBytes[offset++] = (byte)((HeaderVersion >> 8) & 0xFF);
|
|
|
|
pixelBytes[offset++] = (byte)(Flags & 0xFF);
|
|
pixelBytes[offset++] = (byte)((Flags >> 8) & 0xFF);
|
|
|
|
pixelBytes[offset++] = (byte)(payloadLength & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 8) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 16) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 24) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 32) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 40) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 48) & 0xFF);
|
|
pixelBytes[offset++] = (byte)((payloadLength >> 56) & 0xFF);
|
|
|
|
Buffer.BlockCopy(payloadHash, 0, pixelBytes, offset, 32);
|
|
offset += 32;
|
|
|
|
var buffer = new byte[65536];
|
|
int read;
|
|
while ((read = payloadStream.Read(buffer, 0, buffer.Length)) > 0)
|
|
{
|
|
if (offset + read > pixelBytes.Length)
|
|
{
|
|
throw new InvalidDataException("BMP 载荷长度超出预期像素缓冲。");
|
|
}
|
|
|
|
Buffer.BlockCopy(buffer, 0, pixelBytes, offset, read);
|
|
offset += read;
|
|
}
|
|
|
|
if (offset != totalBytes)
|
|
{
|
|
throw new InvalidDataException("BMP 载荷长度与声明的长度不一致。");
|
|
}
|
|
|
|
using (var bitmap = new Bitmap(FixedWidth, height, PixelFormat.Format8bppIndexed))
|
|
{
|
|
var palette = bitmap.Palette;
|
|
for (var index = 0; index < 256; index++)
|
|
{
|
|
palette.Entries[index] = Color.FromArgb(index, index, index);
|
|
}
|
|
|
|
bitmap.Palette = palette;
|
|
var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
|
|
var bitmapData = bitmap.LockBits(rect, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed);
|
|
try
|
|
{
|
|
for (var row = 0; row < height; row++)
|
|
{
|
|
var sourceOffset = row * FixedWidth;
|
|
var destination = IntPtr.Add(bitmapData.Scan0, row * bitmapData.Stride);
|
|
Marshal.Copy(pixelBytes, sourceOffset, destination, FixedWidth);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
bitmap.UnlockBits(bitmapData);
|
|
}
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(outputPath) ?? string.Empty);
|
|
bitmap.Save(outputPath, ImageFormat.Bmp);
|
|
}
|
|
}
|
|
|
|
private static byte[] BuildBlob(byte[] payloadBytes)
|
|
{
|
|
payloadBytes = payloadBytes ?? new byte[0];
|
|
var payloadHash = Convert.FromBase64String(HashUtility.ComputeSha256Base64(payloadBytes));
|
|
using (var stream = new MemoryStream())
|
|
using (var writer = new BinaryWriter(stream, Encoding.ASCII))
|
|
{
|
|
writer.Write(Encoding.ASCII.GetBytes(Magic));
|
|
writer.Write(HeaderVersion);
|
|
writer.Write(Flags);
|
|
writer.Write((ulong)payloadBytes.Length);
|
|
writer.Write(payloadHash);
|
|
writer.Write(payloadBytes);
|
|
writer.Flush();
|
|
return stream.ToArray();
|
|
}
|
|
}
|
|
|
|
private static byte[] ParseBlob(byte[] blobBytes)
|
|
{
|
|
using (var stream = new MemoryStream(blobBytes))
|
|
using (var reader = new BinaryReader(stream, Encoding.ASCII))
|
|
{
|
|
var magic = Encoding.ASCII.GetString(reader.ReadBytes(8));
|
|
if (!string.Equals(magic, Magic, StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidDataException("BMP 差异文件魔数无效。");
|
|
}
|
|
|
|
var version = reader.ReadUInt16();
|
|
if (version != HeaderVersion)
|
|
{
|
|
throw new InvalidDataException("BMP 差异文件版本不受支持。");
|
|
}
|
|
|
|
_ = reader.ReadUInt16();
|
|
var payloadLength = reader.ReadUInt64();
|
|
var payloadHash = reader.ReadBytes(32);
|
|
var payloadBytes = reader.ReadBytes((int)payloadLength);
|
|
var actualHash = Convert.FromBase64String(HashUtility.ComputeSha256Base64(payloadBytes));
|
|
if (!BytesEqual(payloadHash, actualHash))
|
|
{
|
|
throw new InvalidDataException("BMP 差异文件载荷校验失败。");
|
|
}
|
|
|
|
return payloadBytes;
|
|
}
|
|
}
|
|
|
|
private static bool BytesEqual(byte[] left, byte[] right)
|
|
{
|
|
if (left == null || right == null || left.Length != right.Length)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
for (var index = 0; index < left.Length; index++)
|
|
{
|
|
if (left[index] != right[index])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
}
|