/* * Copyright 2016 The WebRTC Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ #ifndef RTC_BASE_COPY_ON_WRITE_BUFFER_H_ #define RTC_BASE_COPY_ON_WRITE_BUFFER_H_ #include #include #include #include #include #include #include "absl/strings/string_view.h" #include "api/scoped_refptr.h" #include "rtc_base/buffer.h" #include "rtc_base/checks.h" #include "rtc_base/ref_counted_object.h" #include "rtc_base/system/rtc_export.h" #include "rtc_base/type_traits.h" namespace rtc { class RTC_EXPORT CopyOnWriteBuffer { public: // An empty buffer. CopyOnWriteBuffer(); // Share the data with an existing buffer. CopyOnWriteBuffer(const CopyOnWriteBuffer& buf); // Move contents from an existing buffer. CopyOnWriteBuffer(CopyOnWriteBuffer&& buf) noexcept; // Construct a buffer from a string, convenient for unittests. explicit CopyOnWriteBuffer(absl::string_view s); // Construct a buffer with the specified number of uninitialized bytes. explicit CopyOnWriteBuffer(size_t size); CopyOnWriteBuffer(size_t size, size_t capacity); // Construct a buffer and copy the specified number of bytes into it. The // source array may be (const) uint8_t*, int8_t*, or char*. template ::value>::type* = nullptr> CopyOnWriteBuffer(const T* data, size_t size) : CopyOnWriteBuffer(data, size, size) {} template ::value>::type* = nullptr> CopyOnWriteBuffer(const T* data, size_t size, size_t capacity) : CopyOnWriteBuffer(size, capacity) { if (buffer_) { std::memcpy(buffer_->data(), data, size); offset_ = 0; size_ = size; } } // Construct a buffer from the contents of an array. template ::value>::type* = nullptr> CopyOnWriteBuffer(const T (&array)[N]) // NOLINT: runtime/explicit : CopyOnWriteBuffer(array, N) {} // Construct a buffer from a vector like type. template ().data())>, typename std::enable_if_t< !std::is_same::value && HasDataAndSize::value && internal::BufferCompat::value>* = nullptr> explicit CopyOnWriteBuffer(const VecT& v) : CopyOnWriteBuffer(v.data(), v.size()) {} // Construct a buffer from a vector like type and a capacity argument template ().data())>, typename std::enable_if_t< !std::is_same::value && HasDataAndSize::value && internal::BufferCompat::value>* = nullptr> explicit CopyOnWriteBuffer(const VecT& v, size_t capacity) : CopyOnWriteBuffer(v.data(), v.size(), capacity) {} ~CopyOnWriteBuffer(); // Get a pointer to the data. Just .data() will give you a (const) uint8_t*, // but you may also use .data() and .data(). template ::value>::type* = nullptr> const T* data() const { return cdata(); } // Get writable pointer to the data. This will create a copy of the underlying // data if it is shared with other buffers. template ::value>::type* = nullptr> T* MutableData() { RTC_DCHECK(IsConsistent()); if (!buffer_) { return nullptr; } UnshareAndEnsureCapacity(capacity()); return buffer_->data() + offset_; } // Get const pointer to the data. This will not create a copy of the // underlying data if it is shared with other buffers. template ::value>::type* = nullptr> const T* cdata() const { RTC_DCHECK(IsConsistent()); if (!buffer_) { return nullptr; } return buffer_->data() + offset_; } bool empty() const { return size_ == 0; } size_t size() const { RTC_DCHECK(IsConsistent()); return size_; } size_t capacity() const { RTC_DCHECK(IsConsistent()); return buffer_ ? buffer_->capacity() - offset_ : 0; } CopyOnWriteBuffer& operator=(const CopyOnWriteBuffer& buf) { RTC_DCHECK(IsConsistent()); RTC_DCHECK(buf.IsConsistent()); if (&buf != this) { buffer_ = buf.buffer_; offset_ = buf.offset_; size_ = buf.size_; } return *this; } CopyOnWriteBuffer& operator=(CopyOnWriteBuffer&& buf) { RTC_DCHECK(IsConsistent()); RTC_DCHECK(buf.IsConsistent()); buffer_ = std::move(buf.buffer_); offset_ = buf.offset_; size_ = buf.size_; buf.offset_ = 0; buf.size_ = 0; return *this; } bool operator==(const CopyOnWriteBuffer& buf) const; bool operator!=(const CopyOnWriteBuffer& buf) const { return !(*this == buf); } uint8_t operator[](size_t index) const { RTC_DCHECK_LT(index, size()); return cdata()[index]; } // Replace the contents of the buffer. Accepts the same types as the // constructors. template ::value>::type* = nullptr> void SetData(const T* data, size_t size) { RTC_DCHECK(IsConsistent()); if (!buffer_) { buffer_ = size > 0 ? new RefCountedBuffer(data, size) : nullptr; } else if (!buffer_->HasOneRef()) { buffer_ = new RefCountedBuffer(data, size, capacity()); } else { buffer_->SetData(data, size); } offset_ = 0; size_ = size; RTC_DCHECK(IsConsistent()); } template ::value>::type* = nullptr> void SetData(const T (&array)[N]) { SetData(array, N); } void SetData(const CopyOnWriteBuffer& buf) { RTC_DCHECK(IsConsistent()); RTC_DCHECK(buf.IsConsistent()); if (&buf != this) { buffer_ = buf.buffer_; offset_ = buf.offset_; size_ = buf.size_; } } // Append data to the buffer. Accepts the same types as the constructors. template ::value>::type* = nullptr> void AppendData(const T* data, size_t size) { RTC_DCHECK(IsConsistent()); if (!buffer_) { buffer_ = new RefCountedBuffer(data, size); offset_ = 0; size_ = size; RTC_DCHECK(IsConsistent()); return; } UnshareAndEnsureCapacity(std::max(capacity(), size_ + size)); buffer_->SetSize(offset_ + size_); // Remove data to the right of the slice. buffer_->AppendData(data, size); size_ += size; RTC_DCHECK(IsConsistent()); } template ::value>::type* = nullptr> void AppendData(const T (&array)[N]) { AppendData(array, N); } template ().data())>, typename std::enable_if_t< HasDataAndSize::value && internal::BufferCompat::value>* = nullptr> void AppendData(const VecT& v) { AppendData(v.data(), v.size()); } // Sets the size of the buffer. If the new size is smaller than the old, the // buffer contents will be kept but truncated; if the new size is greater, // the existing contents will be kept and the new space will be // uninitialized. void SetSize(size_t size); // Ensure that the buffer size can be increased to at least capacity without // further reallocation. (Of course, this operation might need to reallocate // the buffer.) void EnsureCapacity(size_t capacity); // Resets the buffer to zero size without altering capacity. Works even if the // buffer has been moved from. void Clear(); // Swaps two buffers. friend void swap(CopyOnWriteBuffer& a, CopyOnWriteBuffer& b) { a.buffer_.swap(b.buffer_); std::swap(a.offset_, b.offset_); std::swap(a.size_, b.size_); } CopyOnWriteBuffer Slice(size_t offset, size_t length) const { CopyOnWriteBuffer slice(*this); RTC_DCHECK_LE(offset, size_); RTC_DCHECK_LE(length + offset, size_); slice.offset_ += offset; slice.size_ = length; return slice; } private: using RefCountedBuffer = FinalRefCountedObject; // Create a copy of the underlying data if it is referenced from other Buffer // objects or there is not enough capacity. void UnshareAndEnsureCapacity(size_t new_capacity); // Pre- and postcondition of all methods. bool IsConsistent() const { if (buffer_) { return buffer_->capacity() > 0 && offset_ <= buffer_->size() && offset_ + size_ <= buffer_->size(); } else { return size_ == 0 && offset_ == 0; } } // buffer_ is either null, or points to an rtc::Buffer with capacity > 0. scoped_refptr buffer_; // This buffer may represent a slice of a original data. size_t offset_; // Offset of a current slice in the original data in buffer_. // Should be 0 if the buffer_ is empty. size_t size_; // Size of a current slice in the original data in buffer_. // Should be 0 if the buffer_ is empty. }; } // namespace rtc #endif // RTC_BASE_COPY_ON_WRITE_BUFFER_H_