// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
using System;
using System.IO;
using System.Text;
using System.Runtime.InteropServices;
namespace Microsoft.Research.SEAL
{
///
/// A type to describe the compression algorithm applied to serialized data.
/// Ciphertext and key data consist of a large number of 64-bit words storing
/// integers modulo prime numbers much smaller than the word size, resulting in
/// a large number of zero bytes in the output. Any compression algorithm should
/// be able to clean up these zero bytes and hence compress both ciphertext and
/// key data.
///
public enum ComprModeType : byte
{
/// No compression is used.
None = 0,
/// Use Deflate compression.
Deflate = 1,
}
/// Class to provide functionality for serialization.
///
/// Class to provide functionality for serialization. Most users of the library
/// should never have to call these functions explicitly, as they are called
/// internally by functions such as Ciphertext.Save and Ciphertext.Load.
///
public abstract class Serialization
{
///
/// The compression mode used by default.
///
public static readonly ComprModeType ComprModeDefault = ((Func)(() => {
NativeMethods.Serialization_ComprModeDefault(out byte comprMode);
return (ComprModeType)comprMode;
}))();
/// The magic value indicating a Microsoft SEAL header.
public static readonly ushort SEALMagic = ((Func)(() => {
NativeMethods.Serialization_SEALMagic(out ushort sealMagic);
return sealMagic;
}))();
/// The size in bytes of the SEALHeader.
public static readonly byte SEALHeaderSize = ((Func)(() => {
NativeMethods.Serialization_SEALHeaderSize(out byte sealHeaderSize);
return sealHeaderSize;
}))();
/// Struct to contain header information for serialization.
///
/// Struct to contain header information for serialization. The size of the header is 16 bytes and it consists
/// of the following fields:
///
/// 1. a magic number identifying this is a SEALHeader struct (2 bytes)
/// 2. size in bytes of the SEALHeader struct (1 byte)
/// 3. Microsoft SEAL's major version number (1 byte)
/// 4. Microsoft SEAL's minor version number (1 byte)
/// 5. a ComprModeType indicating whether data after the header is compressed (1 byte)
/// 6. reserved for future use and data alignment (2 bytes)
/// 7. the size in bytes of the entire serialized object, including the header (8 bytes)
///
[StructLayout(LayoutKind.Explicit, Size=16)]
public class SEALHeader : ISettable
{
/// A magic number identifying this is a SEALHeader struct (2 bytes)
[FieldOffset(0)]public ushort Magic = SEALMagic;
/// Size in bytes of the SEALHeader struct (1 byte)
[FieldOffset(2)]public byte HeaderSize = SEALHeaderSize;
/// Microsoft SEAL's major version number (1 byte)
[FieldOffset(3)]public byte VersionMajor = SEALVersion.Major;
/// Microsoft SEAL's minor version number (1 byte)
[FieldOffset(4)]public byte VersionMinor = SEALVersion.Minor;
/// A compr_mode_type indicating whether data after the header is compressed (1 byte)
[FieldOffset(5)]public ComprModeType ComprMode = ComprModeDefault;
/// Reserved for future use and data alignment (2 bytes)
[FieldOffset(6)]public ushort Reserved = 0;
/// The size in bytes of the entire serialized object, including the header (8 bytes)
[FieldOffset(8)]public ulong Size = 0;
///
/// Copies a given SEALHeader to the current one.
///
/// The SEALHeader to copy from
/// if assign is null
public void Set(SEALHeader assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
Magic = assign.Magic;
HeaderSize = assign.HeaderSize;
VersionMajor = assign.VersionMajor;
VersionMinor = assign.VersionMinor;
ComprMode = assign.ComprMode;
Reserved = assign.Reserved;
Size = assign.Size;
}
};
private static bool IsSupportedComprMode(byte comprMode)
{
NativeMethods.Serialization_IsSupportedComprMode(comprMode, out bool result);
return result;
}
/// Returns true if the given value corresponds to a supported compression mode.
/// The compression mode to validate
public static bool IsSupportedComprMode(ComprModeType comprMode) =>
IsSupportedComprMode((byte)comprMode);
/// Returns true if the SEALHeader has a version number compatible with this version of
/// Microsoft SEAL.
/// The SEALHeader
public static bool IsCompatibleVersion(SEALHeader header)
{
byte[] headerArray = new byte[SEALHeaderSize];
using (MemoryStream stream = new MemoryStream(headerArray))
{
SaveHeader(header, stream);
NativeMethods.Serialization_IsCompatibleVersion(
headerArray, (ulong)headerArray.Length, out bool result);
return result;
}
}
/// Returns true if the given SEALHeader is valid for this version of Microsoft SEAL.
/// The SEALHeader
public static bool IsValidHeader(SEALHeader header)
{
byte[] headerArray = new byte[SEALHeaderSize];
using (MemoryStream stream = new MemoryStream(headerArray))
{
SaveHeader(header, stream);
NativeMethods.Serialization_IsValidHeader(
headerArray, (ulong)headerArray.Length, out bool result);
return result;
}
}
/// Saves a SEALHeader to a given binary stream.
///
/// Saves a SEALHeader to a given stream. The output is in binary format and not human-readable.
///
/// The SEALHeader to save to the stream
/// The stream to save the SEALHeader to
/// if header or stream is null
/// if the stream is closed or does not support writing
/// if I/O operations failed
public static void SaveHeader(SEALHeader header, Stream stream)
{
if (null == header)
throw new ArgumentNullException(nameof(header));
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanWrite)
throw new ArgumentException(nameof(stream));
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true))
{
writer.Write(header.Magic);
writer.Write(header.HeaderSize);
writer.Write(header.VersionMajor);
writer.Write(header.VersionMinor);
writer.Write((byte)header.ComprMode);
writer.Write(header.Reserved);
writer.Write(header.Size);
}
}
/// Loads a SEALHeader from a given stream.
/// The stream to load the SEALHeader from
/// The SEALHeader to populate with the loaded data
/// If the loaded SEALHeader is invalid, attempt to identify its format and
/// upgrade to the current SEALHeader version
/// if header or stream is null
/// if the stream is closed or does not support reading
/// if the loaded data is not a valid SEALHeader or if the loaded
/// compression mode is not supported
/// if the stream ended unexpectedly
/// if I/O operations failed
public static void LoadHeader(Stream stream, SEALHeader header, bool tryUpgradeIfInvalid = true)
{
if (null == header)
throw new ArgumentNullException(nameof(header));
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (!stream.CanRead)
throw new ArgumentException(nameof(stream));
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true))
{
header.Magic = reader.ReadUInt16();
header.HeaderSize = reader.ReadByte();
header.VersionMajor = reader.ReadByte();
header.VersionMinor = reader.ReadByte();
header.ComprMode = (ComprModeType)reader.ReadByte();
header.Reserved = reader.ReadUInt16();
header.Size = reader.ReadUInt64();
}
// If header is invalid this may be an older header and we can try to automatically upgrade it
if (tryUpgradeIfInvalid && !IsValidHeader(header))
{
// Try interpret the data as a Microsoft SEAL 3.4 header
LegacyHeaders.SEALHeader_3_4 header_3_4 = new LegacyHeaders.SEALHeader_3_4(header);
SEALHeader newHeader = new SEALHeader();
// Copy over the fields; of course the result may not be valid depending on whether the input was a
// valid version 3.4 header
newHeader.ComprMode = header_3_4.ComprMode;
newHeader.Size = header_3_4.Size;
// Now validate the new header and discard if still not valid; something else is probably wrong
if (IsValidHeader(newHeader))
{
header.Set(newHeader);
}
}
}
internal delegate void SaveDelegate(
byte[] outptr, ulong size, byte comprMode, out long outBytes);
internal delegate void LoadDelegate(
byte[] inptr, ulong size, out long inBytes);
/// Saves data to a given binary stream.
///
/// First this function allocates a buffer of size . The buffer is used by the
/// delegate that writes some number of bytes to the buffer and outputs (in
/// out-parameter) the number of bytes written (less than the size of the buffer). The contents of the buffer
/// are then written to and the function returns the output value of
/// . This function is intended only for internal use.
///
/// The delegate that writes some number of bytes to a given buffer
/// An upper bound on the number of bytes that requires
/// The desired compression mode
/// The destination stream
/// if SaveData or stream is null
/// if the stream is closed or does not support writing, or if size is
/// negative or too large
/// if I/O operations failed
/// if the data to be saved is invalid, if compression mode is not
/// supported, or if compression failed
internal static long Save(SaveDelegate SaveData, long size,
ComprModeType comprMode, Stream stream)
{
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (null == SaveData)
throw new ArgumentNullException(nameof(SaveData));
if (!stream.CanWrite)
throw new ArgumentException(nameof(stream));
if (!IsSupportedComprMode(comprMode))
throw new InvalidOperationException("Unsupported compression mode");
try
{
int sizeInt = checked((int)size);
byte[] buffer = new byte[sizeInt];
SaveData(buffer, checked((ulong)sizeInt), (byte)comprMode, out long outBytes);
int intOutBytes = checked((int)outBytes);
using (BinaryWriter writer = new BinaryWriter(stream, Encoding.UTF8, true))
{
writer.Write(buffer, 0, intOutBytes);
}
// Clear the buffer for safety reasons
Array.Clear(buffer, 0, intOutBytes);
return outBytes;
}
catch (OverflowException ex)
{
throw new ArgumentException($"{nameof(size)} is out of bounds", ex);
}
}
/// Loads data from a given binary stream.
///
/// This function calls the function to first load a object
/// from . The is then read from the
/// and a buffer of corresponding size is allocated. Next, the buffer is filled with
/// data read from and is called with the buffer as input,
/// which outputs (in out-parameter) the number bytes read from the buffer. This should match exactly the size
/// of the buffer. Finally, the function returns the output value of . This function
/// is intended only for internal use.
///
/// The delegate that reads some number of bytes to a given buffer
/// The input stream
/// if LoadData or stream is null
/// if the stream is closed or does not support reading
/// if the stream ended unexpectedly
/// if I/O operations failed
/// if the loaded data is invalid, if the loaded compression mode is
/// not supported, or if size of the object is more than 2 GB
internal static long Load(LoadDelegate LoadData, Stream stream)
{
if (null == stream)
throw new ArgumentNullException(nameof(stream));
if (null == LoadData)
throw new ArgumentNullException(nameof(LoadData));
if (!stream.CanRead)
throw new ArgumentException(nameof(stream));
try
{
SEALHeader header = new SEALHeader();
var pos = stream.Position;
LoadHeader(stream, header);
// Check the validity of the header
if (!IsCompatibleVersion(header))
throw new InvalidOperationException("Incompatible version");
if (!IsSupportedComprMode(header.ComprMode))
throw new InvalidOperationException("Unsupported compression mode");
if (!IsValidHeader(header))
throw new InvalidOperationException("Loaded SEALHeader is invalid");
if (header.Size > checked((ulong)int.MaxValue))
throw new InvalidOperationException("Object size is larger than 2 GB");
int sizeInt = checked((int)header.Size);
stream.Seek(pos, SeekOrigin.Begin);
byte[] buffer = null;
using (BinaryReader reader = new BinaryReader(stream, Encoding.UTF8, true))
{
buffer = reader.ReadBytes(sizeInt);
}
LoadData(buffer, header.Size, out long outBytes);
// Clear the buffer for safety reasons
Array.Clear(buffer, 0, sizeInt);
return outBytes;
}
catch (OverflowException ex)
{
throw new InvalidOperationException("Size indicated by loaded SEALHeader is out of bounds", ex);
}
}
}
/// Class to contain header information for legacy headers.
public abstract class LegacyHeaders
{
/// Class to enable compatibility with Microsoft SEAL 3.4 headers.
[StructLayout(LayoutKind.Explicit, Size=16)]
public class SEALHeader_3_4 : ISettable, ISettable
{
/// SEALMagic
[FieldOffset(0)]public ushort Magic = Serialization.SEALMagic;
/// ZeroByte
[FieldOffset(2)]public byte ZeroByte = 0;
/// ComprModeType
[FieldOffset(3)]public ComprModeType ComprMode = Serialization.ComprModeDefault;
/// Size
[FieldOffset(4)]public uint Size = 0;
/// Reserved
[FieldOffset(8)]public ulong Reserved = 0;
/// Creates a new SEALHeader_3_4.
public SEALHeader_3_4()
{
}
///
/// Constructs a new SEALHeader_3_4 by copying a given one.
///
/// The SEALHeader_3_4 to copy from
/// if copy is null
public SEALHeader_3_4(Serialization.SEALHeader copy)
{
if (null == copy)
throw new ArgumentNullException(nameof(copy));
Set(copy);
}
/// Copies a given SEALHeader_3_4 to the current one.
/// The SEALHeader_3_4 to copy from
/// if assign is null
public void Set(SEALHeader_3_4 assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
Magic = assign.Magic;
ZeroByte = assign.ZeroByte;
ComprMode = assign.ComprMode;
Size = assign.Size;
Reserved = assign.Reserved;
}
/// Copies a given SEALHeader to the current one as a byte array.
/// The SEALHeader to copy from
/// if assign is null
public void Set(Serialization.SEALHeader assign)
{
if (null == assign)
throw new ArgumentNullException(nameof(assign));
GCHandle gch = GCHandle.Alloc(this, GCHandleType.Pinned);
Marshal.StructureToPtr(assign, gch.AddrOfPinnedObject(), false);
gch.Free();
}
};
}
}