forked from chromium/chromium
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BufferIterator is a bounds-checked container utility to access variable- length, heterogeneous structures contained within a buffer. Bug: 932175 Change-Id: I41f81fca3671bc35fbe65d0dc71c8fd4742453c5 Reviewed-on: https://chromium-review.googlesource.com/c/1481654 Reviewed-by: Daniel Cheng <dcheng@chromium.org> Commit-Queue: Robert Sesek <rsesek@chromium.org> Cr-Commit-Position: refs/heads/master@{#636795}
- Loading branch information
Showing
4 changed files
with
388 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
// Copyright 2019 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#ifndef BASE_CONTAINERS_BUFFER_ITERATOR_H_ | ||
#define BASE_CONTAINERS_BUFFER_ITERATOR_H_ | ||
|
||
#include <type_traits> | ||
|
||
#include "base/bit_cast.h" | ||
#include "base/containers/span.h" | ||
#include "base/numerics/checked_math.h" | ||
|
||
namespace base { | ||
|
||
// BufferIterator is a bounds-checked container utility to access variable- | ||
// length, heterogeneous structures contained within a buffer. If the data are | ||
// homogeneous, use base::span<> instead. | ||
// | ||
// After being created with a weakly-owned buffer, BufferIterator returns | ||
// pointers to structured data within the buffer. After each method call that | ||
// returns data in the buffer, the iterator position is advanced by the byte | ||
// size of the object (or span of objects) returned. If there are not enough | ||
// bytes remaining in the buffer to return the requested object(s), a nullptr | ||
// or empty span is returned. | ||
// | ||
// This class is similar to base::Pickle, which should be preferred for | ||
// serializing to disk. Pickle versions its header and does not support writing | ||
// structures, which are problematic for serialization due to struct padding and | ||
// version shear concerns. | ||
// | ||
// Example usage: | ||
// | ||
// std::vector<uint8_t> buffer(4096); | ||
// if (!ReadSomeData(&buffer, buffer.size())) { | ||
// LOG(ERROR) << "Failed to read data."; | ||
// return false; | ||
// } | ||
// | ||
// BufferIterator<uint8_t> iterator(buffer); | ||
// uint32_t* num_items = iterator.Object<uint32_t>(); | ||
// if (!num_items) { | ||
// LOG(ERROR) << "No num_items field." | ||
// return false; | ||
// } | ||
// | ||
// base::span<const item_struct> items = | ||
// iterator.Span<item_struct>(*num_items); | ||
// if (items.size() != *num_items) { | ||
// LOG(ERROR) << "Not enough items."; | ||
// return false; | ||
// } | ||
// | ||
// // ... validate the objects in |items|. | ||
template <typename B> | ||
class BufferIterator { | ||
public: | ||
static_assert(std::is_same<std::remove_const_t<B>, char>::value || | ||
std::is_same<std::remove_const_t<B>, unsigned char>::value, | ||
"Underlying buffer type must be char-type."); | ||
|
||
BufferIterator() {} | ||
BufferIterator(B* data, size_t size) | ||
: BufferIterator(make_span(data, size)) {} | ||
explicit BufferIterator(span<B> buffer) | ||
: buffer_(buffer), remaining_(buffer) {} | ||
~BufferIterator() {} | ||
|
||
// Returns a pointer to a mutable structure T in the buffer at the current | ||
// position. On success, the iterator position is advanced by sizeof(T). If | ||
// there are not sizeof(T) bytes remaining in the buffer, returns nullptr. | ||
template <typename T, | ||
typename = | ||
typename std::enable_if_t<std::is_trivially_copyable<T>::value>> | ||
T* MutableObject() { | ||
size_t size = sizeof(T); | ||
size_t next_position; | ||
if (!CheckAdd(position(), size).AssignIfValid(&next_position)) | ||
return nullptr; | ||
if (next_position > total_size()) | ||
return nullptr; | ||
T* t = bit_cast<T*>(remaining_.data()); | ||
remaining_ = remaining_.subspan(size); | ||
return t; | ||
} | ||
|
||
// Returns a const pointer to an object of type T in the buffer at the current | ||
// position. | ||
template <typename T, | ||
typename = | ||
typename std::enable_if_t<std::is_trivially_copyable<T>::value>> | ||
const T* Object() { | ||
return MutableObject<const T>(); | ||
} | ||
|
||
// Returns a span of |count| T objects in the buffer at the current position. | ||
// On success, the iterator position is advanced by |sizeof(T) * count|. If | ||
// there are not enough bytes remaining in the buffer to fulfill the request, | ||
// returns an empty span. | ||
template <typename T, | ||
typename = | ||
typename std::enable_if_t<std::is_trivially_copyable<T>::value>> | ||
span<T> MutableSpan(size_t count) { | ||
size_t size; | ||
if (!CheckMul(sizeof(T), count).AssignIfValid(&size)) | ||
return span<T>(); | ||
size_t next_position; | ||
if (!CheckAdd(position(), size).AssignIfValid(&next_position)) | ||
return span<T>(); | ||
if (next_position > total_size()) | ||
return span<T>(); | ||
auto result = span<T>(bit_cast<T*>(remaining_.data()), count); | ||
remaining_ = remaining_.subspan(size); | ||
return result; | ||
} | ||
|
||
// Returns a span to |count| const objects of type T in the buffer at the | ||
// current position. | ||
template <typename T, | ||
typename = | ||
typename std::enable_if_t<std::is_trivially_copyable<T>::value>> | ||
span<const T> Span(size_t count) { | ||
return MutableSpan<const T>(count); | ||
} | ||
|
||
// Resets the iterator position to the absolute offset |to|. | ||
void Seek(size_t to) { remaining_ = buffer_.subspan(to); } | ||
|
||
// Returns the total size of the underlying buffer. | ||
size_t total_size() { return buffer_.size(); } | ||
|
||
// Returns the current position in the buffer. | ||
size_t position() { return buffer_.size_bytes() - remaining_.size_bytes(); } | ||
|
||
private: | ||
// The original buffer that the iterator was constructed with. | ||
const span<B> buffer_; | ||
// A subspan of |buffer_| containing the remaining bytes to iterate over. | ||
span<B> remaining_; | ||
// Copy and assign allowed. | ||
}; | ||
|
||
} // namespace base | ||
|
||
#endif // BASE_CONTAINERS_BUFFER_ITERATOR_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
// Copyright 2019 The Chromium Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style license that can be | ||
// found in the LICENSE file. | ||
|
||
#include "base/containers/buffer_iterator.h" | ||
|
||
#include <string.h> | ||
|
||
#include <limits> | ||
#include <vector> | ||
|
||
#include "testing/gtest/include/gtest/gtest.h" | ||
|
||
namespace base { | ||
namespace { | ||
|
||
struct TestStruct { | ||
uint32_t one; | ||
uint8_t two; | ||
}; | ||
|
||
bool operator==(const TestStruct& lhs, const TestStruct& rhs) { | ||
return lhs.one == rhs.one && lhs.two == rhs.two; | ||
} | ||
|
||
TestStruct CreateTestStruct() { | ||
TestStruct expected; | ||
expected.one = 0xabcdef12; | ||
expected.two = 0x34; | ||
return expected; | ||
} | ||
|
||
TEST(BufferIteratorTest, Object) { | ||
TestStruct expected = CreateTestStruct(); | ||
|
||
char buffer[sizeof(TestStruct)]; | ||
memcpy(buffer, &expected, sizeof(buffer)); | ||
|
||
{ | ||
// Read the object. | ||
BufferIterator<char> iterator(buffer, sizeof(buffer)); | ||
const TestStruct* actual = iterator.Object<TestStruct>(); | ||
EXPECT_EQ(expected, *actual); | ||
} | ||
{ | ||
// Iterator's view of the data is not large enough to read the object. | ||
BufferIterator<char> iterator(buffer, sizeof(buffer) - 1); | ||
const TestStruct* actual = iterator.Object<TestStruct>(); | ||
EXPECT_FALSE(actual); | ||
} | ||
} | ||
|
||
TEST(BufferIteratorTest, MutableObject) { | ||
TestStruct expected = CreateTestStruct(); | ||
|
||
char buffer[sizeof(TestStruct)]; | ||
|
||
BufferIterator<char> iterator(buffer, sizeof(buffer)); | ||
|
||
{ | ||
// Write the object. | ||
TestStruct* actual = iterator.MutableObject<TestStruct>(); | ||
actual->one = expected.one; | ||
actual->two = expected.two; | ||
} | ||
|
||
// Rewind the iterator. | ||
iterator.Seek(0); | ||
|
||
{ | ||
// Read the object back. | ||
const TestStruct* actual = iterator.Object<TestStruct>(); | ||
EXPECT_EQ(expected, *actual); | ||
} | ||
} | ||
|
||
TEST(BufferIteratorTest, ObjectSizeOverflow) { | ||
char buffer[64]; | ||
BufferIterator<char> iterator(buffer, std::numeric_limits<size_t>::max()); | ||
|
||
auto* pointer = iterator.Object<uint64_t>(); | ||
EXPECT_TRUE(pointer); | ||
|
||
iterator.Seek(iterator.total_size() - 1); | ||
|
||
auto* invalid_pointer = iterator.Object<uint64_t>(); | ||
EXPECT_FALSE(invalid_pointer); | ||
} | ||
|
||
TEST(BufferIteratorTest, Span) { | ||
TestStruct expected = CreateTestStruct(); | ||
|
||
std::vector<char> buffer(sizeof(TestStruct) * 3); | ||
|
||
{ | ||
// Load the span with data. | ||
BufferIterator<char> iterator(buffer); | ||
span<TestStruct> span = iterator.MutableSpan<TestStruct>(3); | ||
for (auto& ts : span) { | ||
memcpy(&ts, &expected, sizeof(expected)); | ||
} | ||
} | ||
{ | ||
// Read the data back out. | ||
BufferIterator<char> iterator(buffer); | ||
|
||
const TestStruct* actual = iterator.Object<TestStruct>(); | ||
EXPECT_EQ(expected, *actual); | ||
|
||
actual = iterator.Object<TestStruct>(); | ||
EXPECT_EQ(expected, *actual); | ||
|
||
actual = iterator.Object<TestStruct>(); | ||
EXPECT_EQ(expected, *actual); | ||
|
||
EXPECT_EQ(iterator.total_size(), iterator.position()); | ||
} | ||
{ | ||
// Cannot create spans larger than there are data for. | ||
BufferIterator<char> iterator(buffer); | ||
span<const TestStruct> span = iterator.Span<TestStruct>(4); | ||
EXPECT_TRUE(span.empty()); | ||
} | ||
} | ||
|
||
TEST(BufferIteratorTest, SpanOverflow) { | ||
char buffer[64]; | ||
|
||
BufferIterator<char> iterator(buffer, std::numeric_limits<size_t>::max()); | ||
{ | ||
span<const uint64_t> empty_span = iterator.Span<uint64_t>( | ||
(std::numeric_limits<size_t>::max() / sizeof(uint64_t)) + 1); | ||
EXPECT_TRUE(empty_span.empty()); | ||
} | ||
{ | ||
span<const uint64_t> empty_span = | ||
iterator.Span<uint64_t>(std::numeric_limits<size_t>::max()); | ||
EXPECT_TRUE(empty_span.empty()); | ||
} | ||
{ | ||
iterator.Seek(iterator.total_size() - 7); | ||
span<const uint64_t> empty_span = iterator.Span<uint64_t>(1); | ||
EXPECT_TRUE(empty_span.empty()); | ||
} | ||
} | ||
|
||
TEST(BufferIteratorTest, Position) { | ||
char buffer[64]; | ||
BufferIterator<char> iterator(buffer, sizeof(buffer)); | ||
EXPECT_EQ(sizeof(buffer), iterator.total_size()); | ||
|
||
size_t position = iterator.position(); | ||
EXPECT_EQ(0u, position); | ||
|
||
iterator.Object<uint8_t>(); | ||
EXPECT_EQ(sizeof(uint8_t), iterator.position() - position); | ||
position = iterator.position(); | ||
|
||
iterator.Object<uint32_t>(); | ||
EXPECT_EQ(sizeof(uint32_t), iterator.position() - position); | ||
position = iterator.position(); | ||
|
||
iterator.Seek(32); | ||
EXPECT_EQ(32u, iterator.position()); | ||
|
||
EXPECT_EQ(sizeof(buffer), iterator.total_size()); | ||
} | ||
|
||
} // namespace | ||
} // namespace base |
Oops, something went wrong.