diff --git a/include/crucible/bytevector.h b/include/crucible/bytevector.h new file mode 100644 index 0000000..2e1ac4c --- /dev/null +++ b/include/crucible/bytevector.h @@ -0,0 +1,71 @@ +#ifndef _CRUCIBLE_BYTEVECTOR_H_ +#define _CRUCIBLE_BYTEVECTOR_H_ + +#include + +#include +#include + +namespace crucible { + using namespace std; + // new[] is a little slower than malloc + // shared_ptr is about 2x slower than unique_ptr + // vector is ~160x slower + // so we won't bother with unique_ptr because we can't do shared copies with it + + class ByteVector { + public: + using Pointer = shared_ptr; + using value_type = Pointer::element_type; + using iterator = value_type*; + + ByteVector() = default; + ByteVector(size_t size); + ByteVector(const ByteVector &that, size_t start, size_t length); + ByteVector(iterator begin, iterator end, size_t min_size = 0); + + ByteVector at(size_t start, size_t length) const; + + value_type& at(size_t) const; + iterator begin() const; + void clear(); + value_type* data() const; + bool empty() const; + iterator end() const; + value_type& operator[](size_t) const; + size_t size() const; + bool operator==(const ByteVector &that) const; + + // this version of erase only works at the beginning or end of the buffer, else throws exception + void erase(iterator first); + void erase(iterator first, iterator last); + + // An important use case is ioctls that have a fixed-size header struct + // followed by a buffer for further arguments. These templates avoid + // doing reinterpret_casts every time. + template ByteVector(const T& object, size_t min_size); + template T* get() const; + private: + Pointer m_ptr; + size_t m_size = 0; + }; + + template + ByteVector::ByteVector(const T& object, size_t min_size) + { + const auto size = max(min_size, sizeof(T)); + m_ptr = Pointer(static_cast(malloc(size)), free); + memcpy(m_ptr.get(), &object, sizeof(T)); + m_size = size; + } + + template + T* + ByteVector::get() const + { + return reinterpret_cast(data()); + } + +} + +#endif // _CRUCIBLE_BYTEVECTOR_H_ diff --git a/lib/Makefile b/lib/Makefile index c7386de..b3acfa4 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -4,6 +4,7 @@ default: libcrucible.a %.a: Makefile CRUCIBLE_OBJS = \ + bytevector.o \ chatter.o \ city.o \ cleanup.o \ diff --git a/lib/bytevector.cc b/lib/bytevector.cc new file mode 100644 index 0000000..5424120 --- /dev/null +++ b/lib/bytevector.cc @@ -0,0 +1,135 @@ +#include "crucible/bytevector.h" + +#include "crucible/error.h" + +namespace crucible { + using namespace std; + + ByteVector::iterator + ByteVector::begin() const + { + return m_ptr.get(); + } + + ByteVector::iterator + ByteVector::end() const + { + return m_ptr.get() + m_size; + } + + size_t + ByteVector::size() const + { + return m_size; + } + + bool + ByteVector::empty() const + { + return !m_ptr || !m_size; + } + + void + ByteVector::clear() + { + m_ptr.reset(); + m_size = 0; + } + + ByteVector::value_type& + ByteVector::operator[](size_t size) const + { + return m_ptr.get()[size]; + } + + ByteVector::ByteVector(const ByteVector &that, size_t start, size_t length) + { + THROW_CHECK0(out_of_range, that.m_ptr); + THROW_CHECK2(out_of_range, start, that.m_size, start < that.m_size); + THROW_CHECK2(out_of_range, start + length, that.m_size + length, start + length < that.m_size + length); + m_ptr = Pointer(that.m_ptr, that.m_ptr.get() + start); + m_size = length; + } + + ByteVector + ByteVector::at(size_t start, size_t length) const + { + return ByteVector(*this, start, length); + } + + ByteVector::value_type& + ByteVector::at(size_t size) const + { + THROW_CHECK0(out_of_range, m_ptr); + THROW_CHECK2(out_of_range, size, m_size, size < m_size); + return m_ptr.get()[size]; + } + + ByteVector::ByteVector(size_t size) + { + m_ptr = Pointer(static_cast(malloc(size)), free); + // bad_alloc doesn't fit THROW_CHECK's template + THROW_CHECK0(runtime_error, m_ptr); + m_size = size; + } + + ByteVector::ByteVector(iterator begin, iterator end, size_t min_size) + { + const size_t size = end - begin; + const size_t alloc_size = max(size, min_size); + m_ptr = Pointer(static_cast(malloc(alloc_size)), free); + THROW_CHECK0(runtime_error, m_ptr); + m_size = alloc_size; + memcpy(m_ptr.get(), begin, size); + } + + bool + ByteVector::operator==(const ByteVector &that) const + { + if (!m_ptr) { + return !that.m_ptr; + } + if (!that.m_ptr) { + return false; + } + if (m_size != that.m_size) { + return false; + } + if (m_ptr.get() == that.m_ptr.get()) { + return true; + } + return !memcmp(m_ptr.get(), that.m_ptr.get(), m_size); + } + + void + ByteVector::erase(iterator begin, iterator end) + { + const size_t size = end - begin; + if (!size) return; + THROW_CHECK0(out_of_range, m_ptr); + const iterator my_begin = m_ptr.get(); + const iterator my_end = my_begin + m_size; + THROW_CHECK4(out_of_range, my_begin, begin, my_end, end, my_begin == begin || my_end == end); + if (begin == my_begin) { + if (end == my_end) { + m_size = 0; + m_ptr.reset(); + return; + } + m_ptr = Pointer(m_ptr, end); + } + m_size -= size; + } + + void + ByteVector::erase(iterator begin) + { + erase(begin, begin + 1); + } + + ByteVector::value_type* + ByteVector::data() const + { + return m_ptr.get(); + } +}