-
Notifications
You must be signed in to change notification settings - Fork 3
snappy framing #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
snappy framing #50
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /** | ||
| * Copyright Quadrivium LLC | ||
| * All Rights Reserved | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <libp2p/basic/read.hpp> | ||
| #include <libp2p/basic/write.hpp> | ||
| #include <qtils/byte_arr.hpp> | ||
|
|
||
| namespace lean { | ||
| inline libp2p::CoroOutcome<void> writeResponseStatus( | ||
| std::shared_ptr<libp2p::basic::Writer> writer) { | ||
| qtils::ByteArr<1> status{0}; | ||
| BOOST_OUTCOME_CO_TRY(co_await libp2p::write(writer, status)); | ||
| co_return outcome::success(); | ||
| } | ||
|
|
||
| inline libp2p::CoroOutcome<void> readResponseStatus( | ||
| std::shared_ptr<libp2p::basic::Reader> reader) { | ||
| qtils::ByteArr<1> status; | ||
| BOOST_OUTCOME_CO_TRY(co_await libp2p::read(reader, status)); | ||
| co_return outcome::success(); | ||
turuslan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
Comment on lines
+14
to
+26
|
||
| } // namespace lean | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,13 +8,20 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <snappy.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <boost/endian/conversion.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <crc32c/crc32c.h> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <libp2p/common/saturating.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <qtils/bytes.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <qtils/bytestr.hpp> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace lean { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace lean::snappy { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum class SnappyError { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_TOO_LONG, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_INVALID, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_TRUNCATED, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_UNKNOWN_IDENTIFIER, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_UNKNOWN_TYPE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| UNCOMPRESS_CRC_MISMATCH, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Q_ENUM_ERROR_CODE(SnappyError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using E = decltype(e); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -23,32 +30,129 @@ namespace lean { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_TOO_LONG"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case E::UNCOMPRESS_INVALID: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_INVALID"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case E::UNCOMPRESS_TRUNCATED: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_TRUNCATED"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case E::UNCOMPRESS_UNKNOWN_IDENTIFIER: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_UNKNOWN_IDENTIFIER"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case E::UNCOMPRESS_UNKNOWN_TYPE: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_UNKNOWN_TYPE"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| case E::UNCOMPRESS_CRC_MISMATCH: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return "SnappyError::UNCOMPRESS_CRC_MISMATCH"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| abort(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline qtils::ByteVec snappyCompress(qtils::BytesIn input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constexpr size_t kHeaderSize = 4; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constexpr auto kMaxBlockSize = size_t{1} << 16; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constexpr auto kDefaultMaxSize = size_t{4} << 20; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| constexpr qtils::ByteArr<6> kStreamIdentifier{'s', 'N', 'a', 'P', 'p', 'Y'}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| enum ChunkType : uint8_t { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Stream = 0xFF, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Compressed = 0x00, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Uncompressed = 0x01, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Padding = 0xFE, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline qtils::ByteVec compress(qtils::BytesIn input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::string compressed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| snappy::Compress(qtils::byte2str(input.data()), input.size(), &compressed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ::snappy::Compress( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::byte2str(input.data()), input.size(), &compressed); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return qtils::ByteVec{qtils::str2byte(std::as_const(compressed))}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline outcome::result<qtils::ByteVec> snappyUncompress( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::BytesIn compressed, size_t max_size = 4 << 20) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline outcome::result<qtils::ByteVec> uncompress( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::BytesIn compressed, size_t max_size = kDefaultMaxSize) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t size = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (not snappy::GetUncompressedLength( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (not ::snappy::GetUncompressedLength( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::byte2str(compressed.data()), compressed.size(), &size)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return SnappyError::UNCOMPRESS_INVALID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (size > max_size) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return SnappyError::UNCOMPRESS_TOO_LONG; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::string uncompressed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (not snappy::Uncompress(qtils::byte2str(compressed.data()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| compressed.size(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &uncompressed)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (not ::snappy::Uncompress(qtils::byte2str(compressed.data()), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| compressed.size(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| &uncompressed)) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return SnappyError::UNCOMPRESS_INVALID; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return qtils::ByteVec{qtils::str2byte(std::as_const(uncompressed))}; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } // namespace lean | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| using Crc32 = qtils::ByteArr<4>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline Crc32 hashCrc32(qtils::BytesIn input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto v = crc32c::Crc32c(input.data(), input.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| v = ((v >> 15) | (v << 17)) + 0xa282ead8; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Crc32 crc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boost::endian::store_little_u32(crc.data(), v); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return crc; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inline qtils::ByteVec compressFramed(qtils::BytesIn input) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::ByteVec framed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto write_header = [&](ChunkType type, size_t size) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| framed.putUint8(type); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| qtils::ByteArr<3> size_bytes; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boost::endian::store_little_u24(size_bytes.data(), size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| framed.put(size_bytes); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| write_header(ChunkType::Stream, kStreamIdentifier.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| framed.put(kStreamIdentifier); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| while (not input.empty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto chunk = input.first(std::min(input.size(), kMaxBlockSize)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto crc = hashCrc32(chunk); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| input = input.subspan(chunk.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto compressed = compress(chunk); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| write_header(ChunkType::Compressed, Crc32::size() + compressed.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+95
to
+108
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto write_header = [&](ChunkType type, size_t size) { | |
| framed.putUint8(type); | |
| qtils::ByteArr<3> size_bytes; | |
| boost::endian::store_little_u24(size_bytes.data(), size); | |
| framed.put(size_bytes); | |
| }; | |
| write_header(ChunkType::Stream, kStreamIdentifier.size()); | |
| framed.put(kStreamIdentifier); | |
| while (not input.empty()) { | |
| auto chunk = input.first(std::min(input.size(), kMaxBlockSize)); | |
| auto crc = hashCrc32(chunk); | |
| input = input.subspan(chunk.size()); | |
| auto compressed = compress(chunk); | |
| write_header(ChunkType::Compressed, Crc32::size() + compressed.size()); | |
| auto writeHeader = [&](ChunkType type, size_t size) { | |
| framed.putUint8(type); | |
| qtils::ByteArr<3> size_bytes; | |
| boost::endian::store_little_u24(size_bytes.data(), size); | |
| framed.put(size_bytes); | |
| }; | |
| writeHeader(ChunkType::Stream, kStreamIdentifier.size()); | |
| framed.put(kStreamIdentifier); | |
| while (not input.empty()) { | |
| auto chunk = input.first(std::min(input.size(), kMaxBlockSize)); | |
| auto crc = hashCrc32(chunk); | |
| input = input.subspan(chunk.size()); | |
| auto compressed = compress(chunk); | |
| writeHeader(ChunkType::Compressed, Crc32::size() + compressed.size()); |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing documentation for the new functions. The compressFramed and uncompressFramed functions implement the snappy framing format but lack documentation explaining the format, parameters (especially the max_size parameter and its default value), return values, and potential error conditions.
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Potential integer overflow when calculating header size plus content size. If size is close to SIZE_MAX, the expression kHeaderSize + size could overflow before the comparison check is performed. Consider checking if size > SIZE_MAX - kHeaderSize before performing the addition, or use a saturating arithmetic function.
| if (compressed.size() < kHeaderSize + size) { | |
| if (size > compressed.size() - kHeaderSize) { |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for minimum content size. For compressed and uncompressed chunk types, the content must be at least 4 bytes (CRC size) before calling subspan(Crc32::size()). If the content size is less than 4 bytes, this will cause undefined behavior or a crash.
| or type == ChunkType::Uncompressed) { | |
| or type == ChunkType::Uncompressed) { | |
| if (content.size() < Crc32::size()) { | |
| return SnappyError::UNCOMPRESS_TRUNCATED; | |
| } |
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for uncompressed chunk size. When type is ChunkType::Uncompressed, the uncompressed data size is not checked against max_size before being added to the result. Unlike the compressed case which uses saturating_sub to limit the size, uncompressed chunks could exceed the remaining allowed space.
Copilot
AI
Jan 6, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation to ensure total uncompressed size does not exceed max_size. While individual chunks are checked against the remaining space using saturating_sub, there's no check to verify that adding the uncompressed data to result won't exceed max_size before calling result.put(). This could allow the total result to exceed the specified max_size limit.
| } | |
| } | |
| auto remaining = libp2p::saturating_sub(max_size, result.size()); | |
| if (uncompressed.size() > remaining) { | |
| return SnappyError::UNCOMPRESS_TOO_LONG; | |
| } |
Uh oh!
There was an error while loading. Please reload this page.