public Task WriteJumpTableAsync(CancellationToken ct) { ct.ThrowIfCancellationRequested(); // The jump table is the last page of the file // - it contains the list of (offset, size) of all the levels that are in the file // - it contains any additional attributes (that were only known after writing all the data) // - it repeats a few important values (sequence, header crc, ...) // - it would contain any optional signature or data that is only know after writing the data to disk, and are needed to decode the rest // marks the start of the JT because we will need to compute the checksum later on int startOffset = m_writer.Position; // "JMPT" m_writer.WriteFixed32(SnapshotFormat.JUMP_TABLE_MAGIC_NUMBER); // Page Size (repeated) m_writer.WriteFixed32((uint)m_pageSize); // Sequence Number (repeated) m_writer.WriteFixed64(m_sequence); // Database ID (repeated) m_writer.WriteBytes(m_uid.ToSlice()); // Header CRC (repeated) m_writer.WriteFixed32(m_headerChecksum); int levels = m_levels; m_writer.WriteFixed32((uint)levels); // Level Count for (int level = 0; level < levels; level++) { // Level Offset (from start of file) m_writer.WriteFixed64((ulong)m_jumpTable[level].Key); // Level Size (in bytes) m_writer.WriteFixed64((ulong)m_jumpTable[level].Value); } //TODO: additional attributes! m_writer.WriteFixed32(0); // 0 for now // End Marker m_writer.WriteFixed32(uint.MaxValue); // Checksum int endOffset = m_writer.Position; uint jumpTableChecksum = SnapshotFormat.ComputeChecksum(m_writer[startOffset, endOffset]); m_writer.WriteFixed32(jumpTableChecksum); // optional padding to fill the rest of the page PadPageIfNeeded(SnapshotFormat.PAGE_SIZE, 0xFE); // we are done ! return(TaskHelpers.CompletedTask); }
/// <summary>Write the header to the file</summary> /// <param name="headerFlags"></param> /// <param name="uid"></param> /// <param name="sequence"></param> /// <param name="count"></param> /// <param name="timestamp"></param> /// <param name="attributes"></param> /// <remarks>This needs to be called before writing any level to the file</remarks> public Task WriteHeaderAsync(SnapshotFormat.Flags headerFlags, Uuid128 uid, ulong sequence, long count, long timestamp, IDictionary<string, IFdbTuple> attributes) { // The header will be use on ore more "pages", to simplify the job of loading / peeking at a stream content (no need for fancy buffering, just need to read 4K pages) // > The last page is padded with 0xAAs to detect corruption. m_uid = uid; m_sequence = sequence; m_itemCount = count; m_timestamp = timestamp; // HEADER // - DB_HEADER (64 bytes) // - DB ATTRIBUTES (variable size list of k/v) // - END_MARKER + HEADER_CRC // - PADDING (to fill last page) // DB Header // "PNDB" m_writer.WriteFixed32(SnapshotFormat.HEADER_MAGIC_NUMBER); // v1.0 m_writer.WriteFixed16(1); // major m_writer.WriteFixed16(0); // minor // FLAGS m_writer.WriteFixed64((ulong)headerFlags); // Database ID m_writer.WriteBytes(uid.ToSlice()); // Database Version m_writer.WriteFixed64(sequence); // Number of items in the database m_writer.WriteFixed64((ulong)count); // Database Timestamp m_writer.WriteFixed64((ulong)timestamp); // Page Size m_writer.WriteFixed32(SnapshotFormat.PAGE_SIZE); // Header Size (not known yet and will be filled in later) int offsetToHeaderSize = m_writer.Skip(4); // we should be at the 64 byte mark Contract.Assert(m_writer.Position == SnapshotFormat.HEADER_METADATA_BYTES); // DB Attributes m_writer.WriteFixed32((uint)attributes.Count); foreach (var kvp in attributes) { // Name m_writer.WriteVarbytes(Slice.FromString(kvp.Key)); // Value m_writer.WriteVarbytes(kvp.Value.ToSlice()); } // Mark the end of the header m_writer.WriteFixed32(uint.MaxValue); // we now have the size of the header, and can fill in the blank var headerEnd = m_writer.Position; m_writer.Position = offsetToHeaderSize; // write the header size (includes the CRC) m_writer.WriteFixed32((uint)checked(headerEnd + SnapshotFormat.HEADER_CRC_SIZE)); m_writer.Position = headerEnd; // now we can compute the actual CRC uint headerChecksum = SnapshotFormat.ComputeChecksum(m_writer.ToSlice()); m_writer.WriteFixed32(headerChecksum); m_headerChecksum = headerChecksum; // optional padding to fill the rest of the page PadPageIfNeeded(SnapshotFormat.PAGE_SIZE, 0xFD); return TaskHelpers.CompletedTask; }
public void ReadJumpTable(CancellationToken ct) { ct.ThrowIfCancellationRequested(); if (!m_hasHeader) { throw new InvalidOperationException("Cannot read the Jump Table without reading the Header first!"); } // an empty database will have at least 2 pages: the header and the JT if (m_file.Length < checked (m_pageSize << 1)) { throw ParseError("File size ({0}) is too small to be a valid snapshot", m_file.Length); } // the jumptable is always in the last page of the file and is expected to fit nicely // > file size MUST be evenly divible by page size // > then JT offset will be file.Length - pageSize if (m_file.Length % m_pageSize != 0) { throw ParseError("The file size ({0}) is not a multiple of the page size ({1}), which may be a symptom of truncation", m_file.Length, m_pageSize); } var jumpTableStart = m_file.Length - m_pageSize; Contract.Assert(jumpTableStart % m_pageSize == 0); m_dataEnd = jumpTableStart; var reader = m_file.CreateReader(jumpTableStart, m_pageSize); // "JMPT" var signature = reader.ReadFixed32(); // Page Size (repeated) var pageSizeRepeated = (int)reader.ReadFixed32(); // Sequence Number (repeated) var sequenceRepeated = reader.ReadFixed64(); // Database ID (repeated) var uidRepeated = new Uuid128(reader.ReadBytes(16).GetBytes()); // Header CRC (repeated) var headerChecksumRepeated = reader.ReadFixed32(); // Sanity checks if (signature != SnapshotFormat.JUMP_TABLE_MAGIC_NUMBER) { throw ParseError("Last page does not appear to be the Jump Table"); } if (pageSizeRepeated != m_pageSize) { throw ParseError("Page size in Jump Table does not match the header value"); } if (sequenceRepeated != m_sequence) { throw ParseError("Sequence in Jump Table does not match the header value"); } if (uidRepeated != m_uid) { throw ParseError("Database ID in Jump Table does not match the header value"); } if (headerChecksumRepeated != m_headerChecksum) { throw ParseError("Database ID in Jump Table does not match the header value"); } // read the table itself int levels = (int)reader.ReadFixed32(); if (levels < 0 || levels > 32) { throw ParseError("The number of levels in the snapshot does not appear to be valid"); } var table = new LevelAddress[levels]; for (int level = 0; level < levels; level++) { ulong offset = reader.ReadFixed64(); ulong size = reader.ReadFixed64(); // Offset and Size cannot be negative // Empty levels (size == 0) must have a zero offset // Non empty levels (size > 0) must have a non zero offset that is greater than the headerSize if ((size == 0 && offset != 0) || (size > 0 && offset < m_dataStart)) { throw ParseError("Level in Jump Table has invalid size ({0}) or offset ({1})", size, offset); } if (checked (offset + size) > m_dataEnd) { throw ParseError("Level in Jump Table would end after the end of the file"); } table[level].Offset = offset; table[level].Size = size; table[level].PaddedSize = RoundUp(size, m_pageSize); } // end attributes uint attributeCount = reader.ReadFixed32(); if (attributeCount != 0) { throw new NotImplementedException("Footer attributes not yet implemented!"); } // end marker if (reader.ReadFixed32() != uint.MaxValue) { throw ParseError("Jump Table end marker not found"); } // checksum uint actualChecksum = SnapshotFormat.ComputeChecksum(reader.Base, reader.Offset); uint checksum = reader.ReadFixed32(); if (actualChecksum != checksum) { throw ParseError("Jump Table checksum does not match ({0} != {1}). This may be an indication of data corruption", checksum, actualChecksum); } m_jumpTable = table; m_levels = levels; m_hasJumpTable = true; }
public void ReadHeader(CancellationToken ct) { ct.ThrowIfCancellationRequested(); // minimum header prolog size is 64 but most will only a single page // we can preallocate a full page, and we will resize it later if needed var reader = m_file.CreateReader(0, SnapshotFormat.HEADER_METADATA_BYTES); // "PNDB" var signature = reader.ReadFixed32(); // v1.0 uint major = reader.ReadFixed16(); uint minor = reader.ReadFixed16(); m_version = new Version((int)major, (int)minor); // FLAGS m_dbFlags = (SnapshotFormat.Flags)reader.ReadFixed64(); // Database ID m_uid = new Uuid128(reader.ReadBytes(16).GetBytes()); // Database Version m_sequence = reader.ReadFixed64(); // Number of items in the database m_itemCount = checked ((long)reader.ReadFixed64()); // Database Timestamp m_timestamp = reader.ReadFixed64(); // Page Size m_pageSize = reader.ReadFixed32(); // Header Size m_headerSize = reader.ReadFixed32(); Contract.Assert(!reader.HasMore); #region Sanity checks // Signature if (signature != SnapshotFormat.HEADER_MAGIC_NUMBER) { throw ParseError("Invalid magic number"); } // Version if (m_version.Major != 1) { throw ParseError("Unsupported file version (major)"); } if (m_version.Minor > 0) { throw ParseError("Unsupported file version (minor)"); } // Flags // Page Size if (m_pageSize != UnmanagedHelpers.NextPowerOfTwo(m_pageSize)) { throw ParseError("Page size ({0}) is not a power of two", m_pageSize); } if (m_pageSize < SnapshotFormat.HEADER_METADATA_BYTES) { throw ParseError("Page size ({0}) is too small", m_pageSize); } if (m_pageSize > 1 << 20) { throw ParseError("Page size ({0}) is too big", m_pageSize); } // Header Size if (m_headerSize < 64 + 4 + 4) { throw ParseError("Header size ({0}) is too small", m_headerSize); } if (m_headerSize > m_file.Length) { throw ParseError("Header size is bigger than the file itself ({0} < {1})", m_headerSize, m_file.Length); } if (m_headerSize > 1 << 10) { throw ParseError("Header size ({0}) exceeds the maximum allowed size", m_headerSize); } #endregion // we know the page size and header size, read the rest... // read the rest reader = m_file.CreateReader(0, m_headerSize); reader.Skip(SnapshotFormat.HEADER_METADATA_BYTES); // parse the attributes Contract.Assert(reader.Offset == SnapshotFormat.HEADER_METADATA_BYTES); var attributeCount = checked ((int)reader.ReadFixed32()); if (attributeCount < 0 || attributeCount > 1024) { throw ParseError("Attributes count is invalid"); } var attributes = new Dictionary <string, IFdbTuple>(attributeCount); for (int i = 0; i < attributeCount; i++) { var name = reader.ReadVarbytes().ToSlice(); //TODO: max size ? if (name.IsNullOrEmpty) { throw ParseError("Header attribute name is empty"); } var data = reader.ReadVarbytes().ToSlice(); //TODO: max size + have a small scratch pad buffer for these ? var value = FdbTuple.Unpack(data); attributes.Add(name.ToUnicode(), value); } m_attributes = attributes; // read the header en marker var marker = reader.ReadFixed32(); if (marker != uint.MaxValue) { throw ParseError("Header end marker is invalid"); } // verify the header checksum uint actualHeaderChecksum = SnapshotFormat.ComputeChecksum(reader.Base, reader.Offset); uint headerChecksum = reader.ReadFixed32(); m_headerChecksum = headerChecksum; if (headerChecksum != actualHeaderChecksum) { throw ParseError("The header checksum does not match ({0} != {1}). This may be an indication of data corruption", headerChecksum, actualHeaderChecksum); } m_dataStart = RoundUp(m_headerSize, m_pageSize); m_hasHeader = true; }
/// <summary>Write the header to the file</summary> /// <param name="headerFlags"></param> /// <param name="uid"></param> /// <param name="sequence"></param> /// <param name="count"></param> /// <param name="timestamp"></param> /// <param name="attributes"></param> /// <remarks>This needs to be called before writing any level to the file</remarks> public Task WriteHeaderAsync(SnapshotFormat.Flags headerFlags, Uuid128 uid, ulong sequence, long count, long timestamp, IDictionary <string, IFdbTuple> attributes) { // The header will be use on ore more "pages", to simplify the job of loading / peeking at a stream content (no need for fancy buffering, just need to read 4K pages) // > The last page is padded with 0xAAs to detect corruption. m_uid = uid; m_sequence = sequence; m_itemCount = count; m_timestamp = timestamp; // HEADER // - DB_HEADER (64 bytes) // - DB ATTRIBUTES (variable size list of k/v) // - END_MARKER + HEADER_CRC // - PADDING (to fill last page) // DB Header // "PNDB" m_writer.WriteFixed32(SnapshotFormat.HEADER_MAGIC_NUMBER); // v1.0 m_writer.WriteFixed16(1); // major m_writer.WriteFixed16(0); // minor // FLAGS m_writer.WriteFixed64((ulong)headerFlags); // Database ID m_writer.WriteBytes(uid.ToSlice()); // Database Version m_writer.WriteFixed64(sequence); // Number of items in the database m_writer.WriteFixed64((ulong)count); // Database Timestamp m_writer.WriteFixed64((ulong)timestamp); // Page Size m_writer.WriteFixed32(SnapshotFormat.PAGE_SIZE); // Header Size (not known yet and will be filled in later) int offsetToHeaderSize = m_writer.Skip(4); // we should be at the 64 byte mark Contract.Assert(m_writer.Position == SnapshotFormat.HEADER_METADATA_BYTES); // DB Attributes m_writer.WriteFixed32((uint)attributes.Count); foreach (var kvp in attributes) { // Name m_writer.WriteVarbytes(Slice.FromString(kvp.Key)); // Value m_writer.WriteVarbytes(kvp.Value.ToSlice()); } // Mark the end of the header m_writer.WriteFixed32(uint.MaxValue); // we now have the size of the header, and can fill in the blank var headerEnd = m_writer.Position; m_writer.Position = offsetToHeaderSize; // write the header size (includes the CRC) m_writer.WriteFixed32((uint)checked (headerEnd + SnapshotFormat.HEADER_CRC_SIZE)); m_writer.Position = headerEnd; // now we can compute the actual CRC uint headerChecksum = SnapshotFormat.ComputeChecksum(m_writer.ToSlice()); m_writer.WriteFixed32(headerChecksum); m_headerChecksum = headerChecksum; // optional padding to fill the rest of the page PadPageIfNeeded(SnapshotFormat.PAGE_SIZE, 0xFD); return(TaskHelpers.CompletedTask); }