/* * Copyright 2016 The Cartographer Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ using System.IO.Compression; using System.Text; using System.Text.Json; namespace CartographerSharp.IO; /// /// A reader of the format produced by ProtoStreamWriter. /// public class ProtoStreamReader : IProtoStreamReader, IDisposable { // First eight bytes to identify our proto stream format. private const ulong Magic = 0x7b1d1f7b5bf501db; private static readonly JsonSerializerOptions JsonOptions = new() { PropertyNameCaseInsensitive = true }; private readonly FileStream _fileStream; private bool _disposed; private bool _magicRead; public ProtoStreamReader(string filename) { if (string.IsNullOrEmpty(filename)) throw new ArgumentException("Filename cannot be null or empty", nameof(filename)); if (!File.Exists(filename)) throw new FileNotFoundException($"File not found: {filename}", filename); _fileStream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); ReadMagic(); } private void ReadMagic() { ulong magic = ReadSizeAsLittleEndian(); if (magic != Magic) { throw new InvalidDataException($"Invalid proto stream format. Expected magic: 0x{Magic:X16}, got: 0x{magic:X16}"); } _magicRead = true; } private ulong ReadSizeAsLittleEndian() { ulong size = 0; for (int i = 0; i < 8; i++) { int byteValue = _fileStream.ReadByte(); if (byteValue == -1) throw new EndOfStreamException("Unexpected end of stream while reading size"); size >>= 8; size += (ulong)byteValue << 56; } return size; } private bool Read(out string decompressedData) { decompressedData = string.Empty; if (!_magicRead) { ReadMagic(); } if (_fileStream.Position >= _fileStream.Length) { return false; // EOF } // Read compressed size ulong compressedSize = ReadSizeAsLittleEndian(); if (compressedSize == 0 || compressedSize > int.MaxValue) { return false; } // Read compressed data byte[] compressedBytes = new byte[compressedSize]; int bytesRead = _fileStream.Read(compressedBytes, 0, (int)compressedSize); if (bytesRead != (int)compressedSize) { return false; } // Decompress using GZip using var memoryStream = new MemoryStream(compressedBytes); using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress); using var reader = new StreamReader(gzipStream, Encoding.UTF8); decompressedData = reader.ReadToEnd(); return true; } /// /// Deserializes compressed proto from the pb stream. /// public bool ReadProto(out T? proto) { proto = default; if (!Read(out string decompressedData)) { return false; } try { proto = JsonSerializer.Deserialize(decompressedData, JsonOptions); return proto != null && !EqualityComparer.Default.Equals(proto, default); } catch (JsonException) { proto = default; return false; } } /// /// 'End-of-file' marker for the pb stream. /// public bool Eof => _fileStream.Position >= _fileStream.Length; public void Dispose() { if (!_disposed) { _fileStream?.Dispose(); _disposed = true; } GC.SuppressFinalize(this); } }