[Open Screen] Replace ByteView with a generic Span type.

This replaces ByteView with a generic Span type that is generally
forward compatible with C++20 std::span.  Span permits a view on both
const and non-const uint8_t data.

ByteView becomes an alias for Span<const uint8_t>.  ByteBuffer is an
alias for Span<uint8_t> and is used for APIs that mutate their input.

Updates the public APIs for libcast to use the aliases and removes
remaining dependencies in public headers on absl::Span.

Removing absl::Span from the remainder of the codebase can be done as a
mechanical cleanup (to eliminate unnecessary conversions).

Bug: b/239435405
Change-Id: Ie5de1bcd0c2ec40450b154f5b36e26b3bde4579d
Reviewed-on: https://chromium-review.googlesource.com/c/openscreen/+/4215906
Reviewed-by: Jordan Bayles <jophba@chromium.org>
Commit-Queue: Mark Foltz <mfoltz@chromium.org>
Reviewed-by: Ryan Keane <rwkeane@google.com>
diff --git a/cast/standalone_receiver/decoder.cc b/cast/standalone_receiver/decoder.cc
index 96ad091..2fa3eb9 100644
--- a/cast/standalone_receiver/decoder.cc
+++ b/cast/standalone_receiver/decoder.cc
@@ -10,7 +10,7 @@
 #include <sstream>
 #include <thread>
 
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/osp_logging.h"
 #include "util/std_util.h"
 #include "util/trace_logging.h"
diff --git a/cast/standalone_receiver/decoder.h b/cast/standalone_receiver/decoder.h
index 5d41546..89d407c 100644
--- a/cast/standalone_receiver/decoder.h
+++ b/cast/standalone_receiver/decoder.h
@@ -13,7 +13,7 @@
 #include "absl/types/span.h"
 #include "cast/standalone_receiver/avcodec_glue.h"
 #include "cast/streaming/frame_id.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 
 namespace openscreen {
 namespace cast {
diff --git a/cast/standalone_receiver/dummy_player.cc b/cast/standalone_receiver/dummy_player.cc
index a66fcd7..f76940e 100644
--- a/cast/standalone_receiver/dummy_player.cc
+++ b/cast/standalone_receiver/dummy_player.cc
@@ -6,8 +6,8 @@
 
 #include <chrono>
 
-#include "absl/types/span.h"
 #include "cast/streaming/encoded_frame.h"
+#include "platform/base/span.h"
 #include "platform/base/trivial_clock_traits.h"
 #include "util/chrono_helpers.h"
 #include "util/osp_logging.h"
@@ -29,8 +29,7 @@
 void DummyPlayer::OnFramesReady(int buffer_size) {
   // Consume the next frame.
   buffer_.resize(buffer_size);
-  const EncodedFrame frame =
-      receiver_->ConsumeNextFrame(absl::Span<uint8_t>(buffer_));
+  const EncodedFrame frame = receiver_->ConsumeNextFrame(buffer_);
 
   // Convert the RTP timestamp to a human-readable timestamp (in µs) and log
   // some short information about the frame.
diff --git a/cast/standalone_sender/streaming_opus_encoder.cc b/cast/standalone_sender/streaming_opus_encoder.cc
index 54aeec4..3371816 100644
--- a/cast/standalone_sender/streaming_opus_encoder.cc
+++ b/cast/standalone_sender/streaming_opus_encoder.cc
@@ -10,7 +10,7 @@
 #include <chrono>
 #include <utility>
 
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/chrono_helpers.h"
 
 namespace openscreen {
diff --git a/cast/standalone_sender/streaming_vpx_encoder.cc b/cast/standalone_sender/streaming_vpx_encoder.cc
index ef3f2b8..89da632 100644
--- a/cast/standalone_sender/streaming_vpx_encoder.cc
+++ b/cast/standalone_sender/streaming_vpx_encoder.cc
@@ -14,7 +14,7 @@
 #include "cast/streaming/encoded_frame.h"
 #include "cast/streaming/environment.h"
 #include "cast/streaming/sender.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/chrono_helpers.h"
 #include "util/osp_logging.h"
 #include "util/saturate_cast.h"
@@ -403,7 +403,7 @@
   }
   frame.rtp_timestamp = results.rtp_timestamp;
   frame.reference_time = results.reference_time;
-  frame.data = ByteView(results.payload);
+  frame.data = results.payload;
 
   if (sender_->EnqueueFrame(frame) != Sender::OK) {
     // Since the frame will not be sent, the encoder's frame dependency chain
diff --git a/cast/streaming/encoded_frame.h b/cast/streaming/encoded_frame.h
index 755e000..c024aef 100644
--- a/cast/streaming/encoded_frame.h
+++ b/cast/streaming/encoded_frame.h
@@ -13,8 +13,8 @@
 #include "cast/streaming/frame_id.h"
 #include "cast/streaming/rtp_time.h"
 #include "platform/api/time.h"
-#include "platform/base/byte_view.h"
 #include "platform/base/macros.h"
+#include "platform/base/span.h"
 
 namespace openscreen {
 namespace cast {
diff --git a/cast/streaming/environment.h b/cast/streaming/environment.h
index ccc8447..5937a4a 100644
--- a/cast/streaming/environment.h
+++ b/cast/streaming/environment.h
@@ -13,8 +13,8 @@
 
 #include "platform/api/time.h"
 #include "platform/api/udp_socket.h"
-#include "platform/base/byte_view.h"
 #include "platform/base/ip_address.h"
+#include "platform/base/span.h"
 
 namespace openscreen {
 namespace cast {
diff --git a/cast/streaming/frame_collector.cc b/cast/streaming/frame_collector.cc
index 9e0afb3..e959506 100644
--- a/cast/streaming/frame_collector.cc
+++ b/cast/streaming/frame_collector.cc
@@ -10,7 +10,7 @@
 
 #include "cast/streaming/frame_id.h"
 #include "cast/streaming/rtp_defines.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/osp_logging.h"
 
 namespace openscreen {
@@ -139,7 +139,7 @@
       frame_.owned_data_.insert(frame_.owned_data_.end(), chunk.payload.begin(),
                                 chunk.payload.end());
     }
-    frame_.data = ByteView(frame_.owned_data_);
+    frame_.data = frame_.owned_data_;
   }
 
   return frame_;
diff --git a/cast/streaming/frame_crypto.cc b/cast/streaming/frame_crypto.cc
index 249c833..8c1db68 100644
--- a/cast/streaming/frame_crypto.cc
+++ b/cast/streaming/frame_crypto.cc
@@ -10,7 +10,7 @@
 #include "openssl/crypto.h"
 #include "openssl/err.h"
 #include "openssl/rand.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/big_endian.h"
 #include "util/crypto/openssl_util.h"
 #include "util/crypto/random_bytes.h"
@@ -20,7 +20,7 @@
 namespace cast {
 
 EncryptedFrame::EncryptedFrame() {
-  data = ByteView(owned_data_);
+  data = owned_data_;
 }
 
 EncryptedFrame::~EncryptedFrame() = default;
@@ -28,14 +28,14 @@
 EncryptedFrame::EncryptedFrame(EncryptedFrame&& other) noexcept
     : EncodedFrame(static_cast<EncodedFrame&&>(other)),
       owned_data_(std::move(other.owned_data_)) {
-  data = ByteView(owned_data_);
+  data = owned_data_;
   other.data = ByteView();
 }
 
 EncryptedFrame& EncryptedFrame::operator=(EncryptedFrame&& other) {
   this->EncodedFrame::operator=(static_cast<EncodedFrame&&>(other));
   owned_data_ = std::move(other.owned_data_);
-  data = ByteView(owned_data_);
+  data = owned_data_;
   other.data = ByteView();
   return *this;
 }
@@ -65,14 +65,13 @@
   EncryptedFrame result;
   encoded_frame.CopyMetadataTo(&result);
   result.owned_data_.resize(encoded_frame.data.size());
-  result.data = ByteView(result.owned_data_);
-  EncryptCommon(encoded_frame.frame_id, encoded_frame.data,
-                absl::MakeSpan(result.owned_data_));
+  result.data = result.owned_data_;
+  EncryptCommon(encoded_frame.frame_id, encoded_frame.data, result.owned_data_);
   return result;
 }
 
 void FrameCrypto::Decrypt(const EncryptedFrame& encrypted_frame,
-                          absl::Span<uint8_t> out) const {
+                          ByteBuffer out) const {
   // AES-CTC is symmetric. Thus, decryption back to the plaintext is the same as
   // encrypting the ciphertext; and both are the same size.
   OSP_DCHECK_EQ(encrypted_frame.data.size(), out.size());
@@ -80,8 +79,8 @@
 }
 
 void FrameCrypto::EncryptCommon(FrameId frame_id,
-                                absl::Span<const uint8_t> in,
-                                absl::Span<uint8_t> out) const {
+                                ByteView in,
+                                ByteBuffer out) const {
   OSP_DCHECK(!frame_id.is_null());
   OSP_DCHECK_EQ(in.size(), out.size());
 
diff --git a/cast/streaming/frame_crypto.h b/cast/streaming/frame_crypto.h
index af25bb2..a596412 100644
--- a/cast/streaming/frame_crypto.h
+++ b/cast/streaming/frame_crypto.h
@@ -11,10 +11,10 @@
 #include <array>
 #include <vector>
 
-#include "absl/types/span.h"
 #include "cast/streaming/encoded_frame.h"
 #include "openssl/aes.h"
 #include "platform/base/macros.h"
+#include "platform/base/span.h"
 
 namespace openscreen {
 namespace cast {
@@ -59,8 +59,7 @@
 
   // Decrypts `encrypted_frame` into `out`. `out` must have a sufficiently-sized
   // data buffer (see GetPlaintextSize()).
-  void Decrypt(const EncryptedFrame& encrypted_frame,
-               absl::Span<uint8_t> out) const;
+  void Decrypt(const EncryptedFrame& encrypted_frame, ByteBuffer out) const;
 
   // AES crypto inputs and outputs (for either encrypting or decrypting) are
   // always the same size in bytes. The following are just "documentative code."
@@ -82,9 +81,7 @@
 
   // AES-CTR is symmetric. Thus, the "meat" of both Encrypt() and Decrypt() is
   // the same.
-  void EncryptCommon(FrameId frame_id,
-                     absl::Span<const uint8_t> in,
-                     absl::Span<uint8_t> out) const;
+  void EncryptCommon(FrameId frame_id, ByteView in, ByteBuffer out) const;
 };
 
 }  // namespace cast
diff --git a/cast/streaming/frame_crypto_unittest.cc b/cast/streaming/frame_crypto_unittest.cc
index 1c45229..7778a71 100644
--- a/cast/streaming/frame_crypto_unittest.cc
+++ b/cast/streaming/frame_crypto_unittest.cc
@@ -9,7 +9,7 @@
 #include <vector>
 
 #include "gtest/gtest.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "platform/test/byte_view_test_util.h"
 #include "util/crypto/random_bytes.h"
 
@@ -26,7 +26,7 @@
   std::vector<uint8_t> buffer(
       reinterpret_cast<const uint8_t*>(kPayload),
       reinterpret_cast<const uint8_t*>(kPayload) + sizeof(kPayload));
-  frame0.data = ByteView(buffer);
+  frame0.data = buffer;
   EncodedFrame frame1;
   frame1.frame_id = frame0.frame_id + 1;
   frame1.data = frame0.data;
@@ -55,19 +55,19 @@
   // plaintext is retrieved.
   std::vector<uint8_t> decrypted_frame0_buffer(
       FrameCrypto::GetPlaintextSize(encrypted_frame0));
-  crypto.Decrypt(encrypted_frame0, absl::MakeSpan(decrypted_frame0_buffer));
+  crypto.Decrypt(encrypted_frame0, decrypted_frame0_buffer);
   EncodedFrame decrypted_frame0;
   encrypted_frame0.CopyMetadataTo(&decrypted_frame0);
-  decrypted_frame0.data = ByteView(decrypted_frame0_buffer);
+  decrypted_frame0.data = decrypted_frame0_buffer;
   EXPECT_EQ(frame0.frame_id, decrypted_frame0.frame_id);
   ExpectByteViewsHaveSameBytes(frame0.data, decrypted_frame0.data);
 
   std::vector<uint8_t> decrypted_frame1_buffer(
       FrameCrypto::GetPlaintextSize(encrypted_frame1));
-  crypto.Decrypt(encrypted_frame1, absl::MakeSpan(decrypted_frame1_buffer));
+  crypto.Decrypt(encrypted_frame1, decrypted_frame1_buffer);
   EncodedFrame decrypted_frame1;
   encrypted_frame1.CopyMetadataTo(&decrypted_frame1);
-  decrypted_frame1.data = ByteView(decrypted_frame1_buffer);
+  decrypted_frame1.data = decrypted_frame1_buffer;
   EXPECT_EQ(frame1.frame_id, decrypted_frame1.frame_id);
   ExpectByteViewsHaveSameBytes(frame1.data, decrypted_frame1.data);
 }
diff --git a/cast/streaming/receiver.cc b/cast/streaming/receiver.cc
index bca4033..7dee2ed 100644
--- a/cast/streaming/receiver.cc
+++ b/cast/streaming/receiver.cc
@@ -144,7 +144,7 @@
   return kNoFramesReady;
 }
 
-EncodedFrame Receiver::ConsumeNextFrame(absl::Span<uint8_t> buffer) {
+EncodedFrame Receiver::ConsumeNextFrame(ByteBuffer buffer) {
   TRACE_DEFAULT_SCOPED(TraceCategory::kReceiver);
   // Assumption: The required call to AdvanceToNextFrame() ensures that
   // |last_frame_consumed_| is set to one before the frame to be consumed here.
@@ -163,7 +163,7 @@
   crypto_.Decrypt(encrypted_frame, buffer);
   EncodedFrame frame;
   encrypted_frame.CopyMetadataTo(&frame);
-  frame.data = ByteView(buffer.data(), buffer.size());
+  frame.data = buffer;
   frame.reference_time =
       *entry.estimated_capture_time + ResolveTargetPlayoutDelay(frame_id);
 
diff --git a/cast/streaming/receiver.h b/cast/streaming/receiver.h
index d3269c6..7a6f9cd 100644
--- a/cast/streaming/receiver.h
+++ b/cast/streaming/receiver.h
@@ -29,6 +29,7 @@
 #include "cast/streaming/session_config.h"
 #include "cast/streaming/ssrc.h"
 #include "platform/api/time.h"
+#include "platform/base/span.h"
 #include "util/alarm.h"
 
 namespace openscreen {
@@ -75,7 +76,7 @@
 //       std::vector<uint8_t> buffer;
 //       buffer.resize(next_frame_buffer_size);
 //       openscreen::cast::EncodedFrame encoded_frame =
-//           receiver_->ConsumeNextFrame(absl::Span<uint8_t>(buffer));
+//           receiver_->ConsumeNextFrame(buffer);
 //
 //       display_.RenderFrame(decoder_.DecodeFrame(encoded_frame.data));
 //
@@ -118,13 +119,6 @@
            SessionConfig config);
   ~Receiver() override;
 
-  // Templated implementation of ConsumeNextFrame() for use when absl cannot be
-  // used explicitly.
-  template<typename T>
-  EncodedFrame ConsumeNextFrame(T buffer) {
-    return ConsumeNextFrame(absl::Span<uint8_t>(buffer));
-  }
-
   // ReceiverBase overrides.
   const SessionConfig& config() const override;
   int rtp_timebase() const override;
@@ -133,7 +127,7 @@
   void SetPlayerProcessingTime(Clock::duration needed_time) override;
   void RequestKeyFrame() override;
   int AdvanceToNextFrame() override;
-  EncodedFrame ConsumeNextFrame(absl::Span<uint8_t> buffer) override;
+  EncodedFrame ConsumeNextFrame(ByteBuffer buffer) override;
 
   // Allows setting picture loss indication for testing. In production, this
   // should be done using the config.
diff --git a/cast/streaming/receiver_base.h b/cast/streaming/receiver_base.h
index 21d8d67..2eb739b 100644
--- a/cast/streaming/receiver_base.h
+++ b/cast/streaming/receiver_base.h
@@ -12,6 +12,7 @@
 #include "cast/streaming/session_config.h"
 #include "cast/streaming/ssrc.h"
 #include "platform/api/time.h"
+#include "platform/base/span.h"
 
 namespace openscreen {
 namespace cast {
@@ -94,7 +95,7 @@
   // |buffer| must point to a sufficiently-sized buffer that will be populated
   // with the frame's payload data. Upon return |frame->data| will be set to the
   // portion of the buffer that was populated.
-  virtual EncodedFrame ConsumeNextFrame(absl::Span<uint8_t> buffer) = 0;
+  virtual EncodedFrame ConsumeNextFrame(ByteBuffer buffer) = 0;
 
   // The default "player processing time" amount. See SetPlayerProcessingTime().
   // This value is based on real world experimentation, however may vary
diff --git a/cast/streaming/receiver_packet_router.cc b/cast/streaming/receiver_packet_router.cc
index bb2776c..e94c5fa 100644
--- a/cast/streaming/receiver_packet_router.cc
+++ b/cast/streaming/receiver_packet_router.cc
@@ -8,7 +8,7 @@
 
 #include "cast/streaming/packet_util.h"
 #include "cast/streaming/receiver.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/osp_logging.h"
 #include "util/stringprintf.h"
 
diff --git a/cast/streaming/receiver_unittest.cc b/cast/streaming/receiver_unittest.cc
index 9079021..74ce375 100644
--- a/cast/streaming/receiver_unittest.cc
+++ b/cast/streaming/receiver_unittest.cc
@@ -31,9 +31,9 @@
 #include "gtest/gtest.h"
 #include "platform/api/time.h"
 #include "platform/api/udp_socket.h"
-#include "platform/base/byte_view.h"
 #include "platform/base/error.h"
 #include "platform/base/ip_address.h"
+#include "platform/base/span.h"
 #include "platform/base/udp_packet.h"
 #include "platform/test/byte_view_test_util.h"
 #include "platform/test/fake_clock.h"
@@ -110,7 +110,7 @@
     for (size_t i = 0; i < buffer_.size(); ++i) {
       buffer_[i] = static_cast<uint8_t>(which + static_cast<int>(i));
     }
-    data = ByteView(buffer_);
+    data = buffer_;
   }
 
   static RtpTimeTicks GetRtpStartTime() {
@@ -327,8 +327,7 @@
     const int payload_size = receiver()->AdvanceToNextFrame();
     ASSERT_NE(Receiver::kNoFramesReady, payload_size);
     std::vector<uint8_t> buffer(payload_size);
-    EncodedFrame received_frame =
-        receiver()->ConsumeNextFrame(absl::Span<uint8_t>(buffer));
+    EncodedFrame received_frame = receiver()->ConsumeNextFrame(buffer);
 
     EXPECT_EQ(sent_frame.dependency, received_frame.dependency);
     EXPECT_EQ(sent_frame.frame_id, received_frame.frame_id);
diff --git a/cast/streaming/sender_packet_router.cc b/cast/streaming/sender_packet_router.cc
index f198ca6..46c4348 100644
--- a/cast/streaming/sender_packet_router.cc
+++ b/cast/streaming/sender_packet_router.cc
@@ -9,7 +9,7 @@
 
 #include "cast/streaming/constants.h"
 #include "cast/streaming/packet_util.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "util/chrono_helpers.h"
 #include "util/osp_logging.h"
 #include "util/saturate_cast.h"
diff --git a/cast/streaming/sender_unittest.cc b/cast/streaming/sender_unittest.cc
index 53ea7a4..a3d6541 100644
--- a/cast/streaming/sender_unittest.cc
+++ b/cast/streaming/sender_unittest.cc
@@ -16,7 +16,6 @@
 #include <vector>
 
 #include "absl/types/optional.h"
-#include "absl/types/span.h"
 #include "cast/streaming/compound_rtcp_builder.h"
 #include "cast/streaming/constants.h"
 #include "cast/streaming/encoded_frame.h"
@@ -35,7 +34,7 @@
 #include "cast/streaming/testing/simple_socket_subscriber.h"
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "platform/base/byte_view.h"
+#include "platform/base/span.h"
 #include "platform/test/byte_view_test_util.h"
 #include "platform/test/fake_clock.h"
 #include "platform/test/fake_task_runner.h"
@@ -305,9 +304,9 @@
     // testing should exist elsewhere to confirm frame play-out times with real
     // Receivers.
     decrypted->buffer.resize(FrameCrypto::GetPlaintextSize(encrypted));
-    crypto_.Decrypt(encrypted, absl::MakeSpan(decrypted->buffer));
+    crypto_.Decrypt(encrypted, decrypted->buffer);
     encrypted.CopyMetadataTo(decrypted);
-    decrypted->data = ByteView(decrypted->buffer);
+    decrypted->data = decrypted->buffer;
     incomplete_frames_.erase(frame_id);
     OnFrameComplete(frame_id);
   }
@@ -408,7 +407,7 @@
                           (frame_id - FrameId::first()));
     frame->reference_time = reference_time;
     PopulateFramePayloadBuffer(seed, num_payload_bytes, &frame->buffer);
-    frame->data = ByteView(frame->buffer);
+    frame->data = frame->buffer;
   }
 
   // Confirms that all |sent_frames| exist in |received_frames|, with identical
diff --git a/platform/BUILD.gn b/platform/BUILD.gn
index aabc37a..c2d9f35 100644
--- a/platform/BUILD.gn
+++ b/platform/BUILD.gn
@@ -16,6 +16,7 @@
     "base/interface_info.h",
     "base/ip_address.h",
     "base/location.h",
+    "base/span.h",
     "base/tls_connect_options.h",
     "base/tls_credentials.h",
     "base/tls_listen_options.h",
@@ -238,10 +239,10 @@
   sources = [
     "api/serial_delete_ptr_unittest.cc",
     "api/time_unittest.cc",
-    "base/byte_view_unittest.cc",
     "base/error_unittest.cc",
     "base/ip_address_unittest.cc",
     "base/location_unittest.cc",
+    "base/span_unittest.cc",
     "base/udp_packet_unittest.cc",
   ]
 
diff --git a/platform/base/byte_view.h b/platform/base/byte_view.h
index 0efc131..bbfa8a3 100644
--- a/platform/base/byte_view.h
+++ b/platform/base/byte_view.h
@@ -5,97 +5,9 @@
 #ifndef PLATFORM_BASE_BYTE_VIEW_H_
 #define PLATFORM_BASE_BYTE_VIEW_H_
 
-#include <stddef.h>
+// Backwards-compatibility header for ByteView.  This will be removed once
+// downstream includes are updated.
 
-#include <cassert>
-#include <cstddef>
-#include <cstdint>
-#include <vector>
-
-#include "platform/base/macros.h"
-
-namespace openscreen {
-
-// Contains a pointer and length to a span of continguous and unowned bytes.
-// The underlying data cannot be modified.
-//
-// The API is a slimmed-down version of a C++20 std::span<const uint8_t> and is
-// intended to be forwards-compatible.  Support for iterators and front/back can
-// be added as needed; we don't intend to add support for static extents.
-//
-// NOTES:
-// - Although other span implementations allow passing zero to last(), we do
-//   not, as the behavior is undefined.  Callers should explicitly create an
-//   empty ByteView instead.
-//
-// - operator== is not implemented to align with std::span.  For more
-//   discussion, this blog post has considerations when implementing operators
-//   on types that don't own the data they depend upon:
-//   https://abseil.io/blog/20180531-regular-types
-//
-// - Unit tests that want to compare the bytes behind two ByteViews can use
-//   ExpectByteViewsHaveSameBytes().
-//
-class ByteView {
- public:
-  constexpr ByteView() noexcept = default;
-  constexpr ByteView(const uint8_t* data, size_t count)
-      : data_(data), count_(count) {}
-  explicit ByteView(const std::vector<uint8_t>& v)
-      : data_(v.data()), count_(v.size()) {}
-
-  constexpr ByteView(const ByteView&) noexcept = default;
-  constexpr ByteView& operator=(const ByteView&) noexcept = default;
-  ByteView(ByteView&&) noexcept = default;
-  ByteView& operator=(ByteView&&) noexcept = default;
-  ~ByteView() = default;
-
-  constexpr const uint8_t* data() const noexcept { return data_; }
-
-  constexpr const uint8_t& operator[](size_t idx) const {
-    assert(idx < count_);
-    return *(data_ + idx);
-  }
-
-  constexpr size_t size() const { return count_; }
-
-  [[nodiscard]] constexpr bool empty() const { return count_ == 0; }
-
-  constexpr ByteView first(size_t count) const {
-    assert(count <= count_);
-    return ByteView(data_, count);
-  }
-
-  constexpr ByteView last(size_t count) const {
-    assert(count <= count_);
-    assert(count != 0);
-    return ByteView(data_ + (count_ - count), count);
-  }
-
-  constexpr const uint8_t* begin() const noexcept { return data_; }
-  constexpr const uint8_t* end() const noexcept { return data_ + count_; }
-
-  void remove_prefix(size_t count) noexcept {
-    assert(count_ >= count);
-    data_ += count;
-    count_ -= count;
-  }
-
-  void remove_suffix(size_t count) noexcept {
-    assert(count_ >= count);
-    count_ -= count;
-  }
-
-  constexpr ByteView subspan(size_t offset, size_t count) const {
-    assert(offset + count < count_);
-    return ByteView(data_ + offset, count);
-  }
-
- private:
-  const uint8_t* data_{nullptr};
-  size_t count_{0};
-};
-
-}  // namespace openscreen
+#include "platform/base/span.h"
 
 #endif  // PLATFORM_BASE_BYTE_VIEW_H_
diff --git a/platform/base/byte_view_unittest.cc b/platform/base/byte_view_unittest.cc
deleted file mode 100644
index 4d9b521..0000000
--- a/platform/base/byte_view_unittest.cc
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright 2023 The Chromium Authors
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#include "platform/base/byte_view.h"
-
-#include "gtest/gtest.h"
-
-namespace {
-
-constexpr char kSampleBytes[] = "googleplex";
-const uint8_t* kSampleData =
-    reinterpret_cast<const uint8_t* const>(&kSampleBytes[0]);
-constexpr size_t kSampleSize =
-    sizeof(kSampleBytes) - 1;  // Ignore null terminator.
-
-}  // namespace
-
-namespace openscreen {
-
-TEST(ByteViewTest, TestBasics) {
-  ByteView nullView;
-  EXPECT_EQ(nullView.data(), nullptr);
-  EXPECT_EQ(nullView.size(), size_t{0});
-  EXPECT_TRUE(nullView.empty());
-
-  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
-  EXPECT_EQ(googlePlex.data(), kSampleData);
-  EXPECT_EQ(googlePlex.size(), kSampleSize);
-  EXPECT_FALSE(googlePlex.empty());
-
-  EXPECT_EQ(googlePlex[0], 'g');
-  EXPECT_EQ(googlePlex[9], 'x');
-
-  ByteView copyBytes = googlePlex;
-  EXPECT_EQ(copyBytes.data(), googlePlex.data());
-  EXPECT_EQ(copyBytes.size(), googlePlex.size());
-
-  ByteView firstBytes(googlePlex.first(4));
-  EXPECT_EQ(firstBytes.data(), googlePlex.data());
-  EXPECT_EQ(firstBytes.size(), size_t{4});
-  EXPECT_EQ(firstBytes[0], 'g');
-  EXPECT_EQ(firstBytes[3], 'g');
-
-  ByteView lastBytes(googlePlex.last(4));
-  EXPECT_EQ(lastBytes.data(), googlePlex.data() + 6);
-  EXPECT_EQ(lastBytes.size(), size_t{4});
-  EXPECT_EQ(lastBytes[0], 'p');
-  EXPECT_EQ(lastBytes[3], 'x');
-
-  ByteView middleBytes(googlePlex.subspan(2, 4));
-  EXPECT_EQ(middleBytes.data(), googlePlex.data() + 2);
-  EXPECT_EQ(middleBytes.size(), size_t{4});
-  EXPECT_EQ(middleBytes[0], 'o');
-  EXPECT_EQ(middleBytes[3], 'e');
-}
-
-TEST(ByteViewTest, TestIterators) {
-  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
-  size_t idx = 0;
-
-  for (const uint8_t* it = googlePlex.begin(); it != googlePlex.end(); it++) {
-    EXPECT_EQ(*it, kSampleBytes[idx]);
-    idx++;
-  }
-}
-
-TEST(ByteViewTest, TestRemove) {
-  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
-
-  googlePlex.remove_prefix(2);
-  EXPECT_EQ(googlePlex.size(), size_t{8});
-  EXPECT_EQ(googlePlex[0], 'o');
-
-  googlePlex.remove_suffix(2);
-  EXPECT_EQ(googlePlex.size(), size_t{6});
-  EXPECT_EQ(googlePlex[5], 'l');
-}
-
-}  // namespace openscreen
diff --git a/platform/base/span.h b/platform/base/span.h
new file mode 100644
index 0000000..11edcf5
--- /dev/null
+++ b/platform/base/span.h
@@ -0,0 +1,128 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PLATFORM_BASE_SPAN_H_
+#define PLATFORM_BASE_SPAN_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <cassert>
+#include <type_traits>
+#include <vector>
+
+namespace openscreen {
+
+template <typename T>
+class Span;
+
+// In Open Screen code, use these aliases for the most common types of Spans.
+// These can be converted to use std::span once the library supports C++20.
+using ByteView = Span<const uint8_t>;
+using ByteBuffer = Span<uint8_t>;
+
+namespace internal {
+
+template <typename From, typename To>
+using EnableIfConvertible = std::enable_if_t<
+    std::is_convertible<From (*)[], To (*)[]>::value>;  // NOLINT
+
+}  // namespace internal
+
+// Contains a pointer and length to a span of contiguous data.
+//
+// The API is a slimmed-down version of a C++20 std::span<T> and is intended to
+// be forwards-compatible with very slight modifications.  We don't intend to
+// add support for static extents.
+//
+// NOTES:
+// - Although other span implementations allow passing zero to last(), we do
+//   not, as the behavior is undefined.  Callers should explicitly create an
+//   empty Span instead.
+//
+// - operator== is not implemented to align with std::span.  For more
+//   discussion, this blog post has considerations when implementing operators
+//   on types that don't own the data they depend upon:
+//   https://abseil.io/blog/20180531-regular-types
+//
+// - Unit tests that want to compare the bytes behind two ByteViews can use
+//   ExpectByteViewsHaveSameBytes().
+template <typename T>
+class Span {
+ public:
+  constexpr Span() noexcept = default;
+  constexpr Span(const Span&) noexcept = default;
+  Span(Span&& other) noexcept = default;
+  constexpr Span& operator=(const Span&) noexcept = default;
+  Span& operator=(Span&& other) noexcept = default;
+
+  constexpr Span(T* data, size_t count) : data_(data), count_(count) {}
+
+  template <typename U, typename = internal::EnableIfConvertible<U, T>>
+  Span(std::vector<U>& v) : data_(v.data()), count_(v.size()) {}  // NOLINT
+
+  template <typename U, typename = internal::EnableIfConvertible<U, T>>
+  constexpr Span(const Span<U>& other) noexcept
+      : data_(other.data()), count_(other.size()) {}  // NOLINT
+
+  template <typename U, typename = internal::EnableIfConvertible<U, T>>
+  constexpr Span& operator=(const Span<U>& other) noexcept {
+    data_ = other.data();
+    count_ = other.size();
+    return *this;
+  }
+
+  ~Span() = default;
+
+  constexpr T* data() const noexcept { return data_; }
+
+  constexpr T& operator[](size_t idx) const {
+    assert(idx < count_);
+    return *(data_ + idx);
+  }
+
+  constexpr size_t size() const { return count_; }
+
+  [[nodiscard]] constexpr bool empty() const { return count_ == 0; }
+
+  constexpr Span first(size_t count) const {
+    assert(count <= count_);
+    return Span(data_, count);
+  }
+
+  constexpr Span last(size_t count) const {
+    assert(count <= count_);
+    assert(count != 0);
+    return Span(data_ + (count_ - count), count);
+  }
+
+  constexpr T* begin() const noexcept { return data_; }
+  constexpr T* end() const noexcept { return data_ + count_; }
+  constexpr const T* cbegin() const noexcept { return data_; }
+  constexpr const T* cend() const noexcept { return data_ + count_; }
+
+  void remove_prefix(size_t count) noexcept {
+    assert(count_ >= count);
+    data_ += count;
+    count_ -= count;
+  }
+
+  void remove_suffix(size_t count) noexcept {
+    assert(count_ >= count);
+    count_ -= count;
+  }
+
+  constexpr Span subspan(size_t offset, size_t count) const {
+    assert(offset + count < count_);
+    return Span(data_ + offset, count);
+  }
+
+ private:
+  T* data_{nullptr};
+  size_t count_{0};
+};
+
+}  // namespace openscreen
+
+#endif  // PLATFORM_BASE_SPAN_H_
diff --git a/platform/base/span_unittest.cc b/platform/base/span_unittest.cc
new file mode 100644
index 0000000..f22a3ba
--- /dev/null
+++ b/platform/base/span_unittest.cc
@@ -0,0 +1,152 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "platform/base/span.h"
+
+#include <stdint.h>
+
+#include <array>
+#include <utility>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+namespace {
+
+constexpr char kSampleBytes[] = "googleplex";
+const uint8_t* kSampleData =
+    reinterpret_cast<const uint8_t* const>(&kSampleBytes[0]);
+constexpr size_t kSampleSize =
+    sizeof(kSampleBytes) - 1;  // Ignore null terminator.
+
+}  // namespace
+
+namespace openscreen {
+
+TEST(SpanTest, TestBasics) {
+  ByteView nullView;
+  EXPECT_EQ(nullView.data(), nullptr);
+  EXPECT_EQ(nullView.size(), size_t{0});
+  EXPECT_TRUE(nullView.empty());
+
+  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
+  EXPECT_EQ(googlePlex.data(), kSampleData);
+  EXPECT_EQ(googlePlex.size(), kSampleSize);
+  EXPECT_FALSE(googlePlex.empty());
+
+  EXPECT_EQ(googlePlex[0], 'g');
+  EXPECT_EQ(googlePlex[9], 'x');
+
+  ByteView copyBytes = googlePlex;
+  EXPECT_EQ(copyBytes.data(), googlePlex.data());
+  EXPECT_EQ(copyBytes.size(), googlePlex.size());
+
+  ByteView firstBytes(googlePlex.first(4));
+  EXPECT_EQ(firstBytes.data(), googlePlex.data());
+  EXPECT_EQ(firstBytes.size(), size_t{4});
+  EXPECT_EQ(firstBytes[0], 'g');
+  EXPECT_EQ(firstBytes[3], 'g');
+
+  ByteView lastBytes(googlePlex.last(4));
+  EXPECT_EQ(lastBytes.data(), googlePlex.data() + 6);
+  EXPECT_EQ(lastBytes.size(), size_t{4});
+  EXPECT_EQ(lastBytes[0], 'p');
+  EXPECT_EQ(lastBytes[3], 'x');
+
+  ByteView middleBytes(googlePlex.subspan(2, 4));
+  EXPECT_EQ(middleBytes.data(), googlePlex.data() + 2);
+  EXPECT_EQ(middleBytes.size(), size_t{4});
+  EXPECT_EQ(middleBytes[0], 'o');
+  EXPECT_EQ(middleBytes[3], 'e');
+}
+
+TEST(SpanTest, TestIterators) {
+  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
+  size_t idx = 0;
+
+  for (const uint8_t* it = googlePlex.begin(); it != googlePlex.end(); it++) {
+    EXPECT_EQ(*it, kSampleBytes[idx]);
+    idx++;
+  }
+}
+
+TEST(SpanTest, TestRemove) {
+  ByteView googlePlex = ByteView(kSampleData, kSampleSize);
+
+  googlePlex.remove_prefix(2);
+  EXPECT_EQ(googlePlex.size(), size_t{8});
+  EXPECT_EQ(googlePlex[0], 'o');
+
+  googlePlex.remove_suffix(2);
+  EXPECT_EQ(googlePlex.size(), size_t{6});
+  EXPECT_EQ(googlePlex[5], 'l');
+}
+
+TEST(SpanTest, ConstConversions) {
+  std::array<uint8_t, kSampleSize> mutable_data;
+
+  // Pointer-and-size construction
+  Span<const uint8_t> const_span =
+      Span<const uint8_t>(kSampleData, kSampleSize);
+  Span<const uint8_t> const_span2 =
+      Span<const uint8_t>(mutable_data.data(), mutable_data.size());
+
+  Span<uint8_t> mutable_span =
+      Span<uint8_t>(mutable_data.data(), mutable_data.size());
+
+  // ILLEGAL!
+  // Span<uint8_t> mutable_span =  Span<uint8_t>(kSampleData, kSampleSize);
+
+  // Copy constructors
+  Span<const uint8_t> const_span3(const_span);
+  Span<const uint8_t> const_span4(mutable_span);
+  Span<uint8_t> mutable_span2(mutable_span);
+  Span<uint8_t> mutable_span3(mutable_span);
+
+  // ILLEGAL!
+  // Span<uint8_t> mutable_span3(const_span);
+
+  // Move constructors
+  Span<const uint8_t> const_span5(std::move(const_span3));
+  Span<const uint8_t> const_span6(std::move(mutable_span2));
+  Span<uint8_t> mutable_span4(std::move(mutable_span3));
+
+  // ILLEGAL!
+  // Span<uint8_t> mutable_span5(std::move(const_span4));
+
+  // Copy assignment
+  Span<const uint8_t> const_span7;
+  const_span7 = const_span2;
+
+  Span<const uint8_t> const_span8;
+  const_span8 = mutable_span4;
+
+  // ILLEGAL!
+  // Span<uint8_t> mutable_span6;
+  // mutable_span6 = const_span5;
+
+  Span<uint8_t> mutable_span7;
+  mutable_span7 = mutable_span4;
+
+  // Move assignment
+  Span<const uint8_t> const_span9;
+  const_span9 = std::move(const_span5);
+
+  Span<const uint8_t> const_span10;
+  const_span10 = std::move(mutable_span4);
+
+  // ILLEGAL!
+  // Span<uint8_t> mutable_span8;
+  // mutable_span8 = std::move(const_span5);
+
+  Span<uint8_t> mutable_span9;
+  mutable_span9 = std::move(mutable_span4);
+
+  // Vector construction
+  std::vector<uint8_t> mutable_vector({1, 2, 3, 4});
+  Span<const uint8_t> const_span11(mutable_vector);
+  Span<uint8_t> mutable_span10(mutable_vector);
+}
+
+}  // namespace openscreen
diff --git a/platform/test/byte_view_test_util.cc b/platform/test/byte_view_test_util.cc
index dddbda3..ece7227 100644
--- a/platform/test/byte_view_test_util.cc
+++ b/platform/test/byte_view_test_util.cc
@@ -7,7 +7,6 @@
 #include <cstring>
 
 #include "gtest/gtest.h"
-#include "platform/base/byte_view.h"
 
 namespace openscreen {
 namespace {
diff --git a/platform/test/byte_view_test_util.h b/platform/test/byte_view_test_util.h
index 5bd4af1..4e7b528 100644
--- a/platform/test/byte_view_test_util.h
+++ b/platform/test/byte_view_test_util.h
@@ -5,9 +5,9 @@
 #ifndef PLATFORM_TEST_BYTE_VIEW_TEST_UTIL_H_
 #define PLATFORM_TEST_BYTE_VIEW_TEST_UTIL_H_
 
-namespace openscreen {
+#include "platform/base/span.h"
 
-class ByteView;
+namespace openscreen {
 
 // Asserts that `first` and `second` have the same non-zero length, and are
 // views over the same bytes.  (Not that they are the pointers to the same