Reorganize upb file structure

This change moves almost everything in the `upb/` directory up one level, so
that for example `upb/upb/generated_code_support.h` becomes just
`upb/generated_code_support.h`. The only exceptions I made to this were that I
left `upb/cmake` and `upb/BUILD` where they are, mostly because that avoids
conflict with other files and the current locations seem reasonable for now.

The `python/` directory is a little bit of a challenge because we had to merge
the existing directory there with `upb/python/`. I made `upb/python/BUILD` into
the BUILD file for the merged directory, and it effectively loads the contents
of the other BUILD file via `python/build_targets.bzl`, but I plan to clean
this up soon.

PiperOrigin-RevId: 568651768
diff --git a/protos/BUILD b/protos/BUILD
new file mode 100644
index 0000000..648d938
--- /dev/null
+++ b/protos/BUILD
@@ -0,0 +1,170 @@
+# Copyright (c) 2009-2021, Google LLC
+# All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+load(
+    "//bazel:build_defs.bzl",
+    "UPB_DEFAULT_CPPOPTS",
+)
+load(
+    "//protos/bazel:upb_cc_proto_library.bzl",
+    "upb_cc_proto_library_copts",
+)
+
+# begin:google_only
+# package(default_applicable_licenses = ["//upb:license"])
+# end:google_only
+
+licenses(["notice"])
+
+cc_library(
+    name = "repeated_field",
+    hdrs = [
+        "repeated_field.h",
+        "repeated_field_iterator.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":protos",
+        ":protos_traits",
+        "//upb:base",
+        "//upb:collections",
+        "//upb:mem",
+        "//upb:message_copy",
+        "//upb:message_types",
+        "//upb:port",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/strings",
+    ],
+)
+
+cc_library(
+    name = "protos",
+    srcs = [
+        "protos.cc",
+    ],
+    hdrs = [
+        "protos.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":protos_extension_lock",
+        "//upb:base",
+        "//upb:mem",
+        "//upb:message_accessors_internal",
+        "//upb:message_copy",
+        "//upb:message_internal",
+        "//upb:message_promote",
+        "//upb:message_types",
+        "//upb:mini_table",
+        "//upb:wire",
+        "//upb:wire_types",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings",
+        "@com_google_absl//absl/strings:str_format",
+    ],
+)
+
+# Internally used type traits.
+cc_library(
+    name = "protos_traits",
+    hdrs = [
+        "protos_traits.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:private"],
+)
+
+cc_library(
+    name = "protos_internal",
+    hdrs = ["protos_internal.h"],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":protos",
+        "//upb:mem",
+        "//upb:message",
+        "//upb:mini_table",
+        "@com_google_absl//absl/status",
+        "@com_google_absl//absl/status:statusor",
+        "@com_google_absl//absl/strings:str_format",
+    ],
+)
+
+cc_library(
+    name = "protos_extension_lock",
+    srcs = ["protos_extension_lock.cc"],
+    hdrs = ["protos_extension_lock.h"],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        "//upb:message",
+        "@com_google_absl//absl/base:core_headers",
+        "@com_google_absl//absl/synchronization",
+    ],
+)
+
+# Common support code for C++ generated code.
+cc_library(
+    name = "generated_protos_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+    hdrs = [
+        "protos_internal.h",
+    ],
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+    deps = [
+        ":protos",
+        ":protos_internal",
+        ":repeated_field",
+        "//upb:mem",
+        "//upb:message",
+    ],
+)
+
+cc_test(
+    name = "protos_internal_test",
+    srcs = ["protos_internal_test.cc"],
+    copts = UPB_DEFAULT_CPPOPTS,
+    deps = [
+        ":protos_internal",
+        "@com_google_googletest//:gtest_main",
+        "//upb:mem",
+        "//protos_generator/tests:test_model_upb_cc_proto",
+        "//protos_generator/tests:test_model_upb_proto",
+    ],
+)
+
+upb_cc_proto_library_copts(
+    name = "upb_cc_proto_library_copts__for_generated_code_only_do_not_use",
+    copts = UPB_DEFAULT_CPPOPTS,
+    visibility = ["//visibility:public"],
+)
+
+cc_test(
+    name = "repeated_field_iterator_test",
+    srcs = ["repeated_field_iterator_test.cc"],
+    deps = [
+        ":repeated_field",
+        "@com_google_googletest//:gtest_main",
+    ],
+)
+
+cc_test(
+    name = "protos_extension_lock_test",
+    srcs = ["protos_extension_lock_test.cc"],
+    deps = [
+        "@com_google_googletest//:gtest_main",
+        "//upb:mem",
+        "//protos",
+        "//protos:protos_extension_lock",
+        "//protos_generator/tests:test_model_upb_cc_proto",
+        "@com_google_absl//absl/hash",
+        "@com_google_absl//absl/log:absl_check",
+    ],
+)
diff --git a/protos/README.md b/protos/README.md
new file mode 100644
index 0000000..dc09201
--- /dev/null
+++ b/protos/README.md
@@ -0,0 +1,7 @@
+`protos` C++ API
+===============
+
+`protos` is a C++ protobuf implementation backed by μpb, introduced in 2022.
+The API is currently experimental and may undergo incompatible changes without
+notice. The supported C++ implementation is
+[here](https://github.com/protocolbuffers/protobuf/tree/main/src).
diff --git a/protos/bazel/BUILD b/protos/bazel/BUILD
new file mode 100644
index 0000000..ea91541
--- /dev/null
+++ b/protos/bazel/BUILD
@@ -0,0 +1,25 @@
+# Copyright (c) 2009-2021, Google LLC
+# All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+# begin:google_only
+# package(default_applicable_licenses = ["//upb:license"])
+# end:google_only
+
+licenses(["notice"])
+
+bzl_library(
+    name = "upb_cc_proto_library_bzl",
+    srcs = ["upb_cc_proto_library.bzl"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "@bazel_skylib//lib:paths",
+        "//bazel:upb_proto_library_bzl",
+        "@bazel_tools//tools/cpp:toolchain_utils.bzl",
+    ],
+)
diff --git a/protos/bazel/upb_cc_proto_library.bzl b/protos/bazel/upb_cc_proto_library.bzl
new file mode 100644
index 0000000..b59ae65
--- /dev/null
+++ b/protos/bazel/upb_cc_proto_library.bzl
@@ -0,0 +1,289 @@
+# Copyright (c) 2009-2021, Google LLC
+# All rights reserved.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Public rules for using upb protos:
+  - upb_cc_proto_library()
+"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load("//bazel:upb_proto_library.bzl", "GeneratedSrcsInfo", "UpbWrappedCcInfo", "upb_proto_library_aspect")
+
+# begin:google_only
+# load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain", "use_cpp_toolchain")
+#
+# end:google_only
+# begin:github_only
+# Compatibility code for Bazel 4.x. Remove this when we drop support for Bazel 4.x.
+load("@bazel_tools//tools/cpp:toolchain_utils.bzl", "find_cpp_toolchain")
+
+def use_cpp_toolchain():
+    return ["@bazel_tools//tools/cpp:toolchain_type"]
+# end:github_only
+
+# Generic support code #########################################################
+
+# begin:github_only
+_is_google3 = False
+# end:github_only
+
+# begin:google_only
+# _is_google3 = True
+# end:google_only
+
+def _get_real_short_path(file):
+    # For some reason, files from other archives have short paths that look like:
+    #   ../com_google_protobuf/google/protobuf/descriptor.proto
+    short_path = file.short_path
+    if short_path.startswith("../"):
+        second_slash = short_path.index("/", 3)
+        short_path = short_path[second_slash + 1:]
+
+    # Sometimes it has another few prefixes like:
+    #   _virtual_imports/any_proto/google/protobuf/any.proto
+    #   benchmarks/_virtual_imports/100_msgs_proto/benchmarks/100_msgs.proto
+    # We want just google/protobuf/any.proto.
+    virtual_imports = "_virtual_imports/"
+    if virtual_imports in short_path:
+        short_path = short_path.split(virtual_imports)[1].split("/", 1)[1]
+    return short_path
+
+def _get_real_root(file):
+    real_short_path = _get_real_short_path(file)
+    return file.path[:-len(real_short_path) - 1]
+
+def _generate_output_file(ctx, src, extension):
+    real_short_path = _get_real_short_path(src)
+    real_short_path = paths.relativize(real_short_path, ctx.label.package)
+    output_filename = paths.replace_extension(real_short_path, extension)
+    ret = ctx.actions.declare_file(output_filename)
+    return ret
+
+def _filter_none(elems):
+    out = []
+    for elem in elems:
+        if elem:
+            out.append(elem)
+    return out
+
+def _cc_library_func(ctx, name, hdrs, srcs, copts, dep_ccinfos):
+    """Like cc_library(), but callable from rules.
+
+    Args:
+      ctx: Rule context.
+      name: Unique name used to generate output files.
+      hdrs: Public headers that can be #included from other rules.
+      srcs: C/C++ source files.
+      copts: Additional options for cc compilation.
+      dep_ccinfos: CcInfo providers of dependencies we should build/link against.
+
+    Returns:
+      CcInfo provider for this compilation.
+    """
+
+    compilation_contexts = [info.compilation_context for info in dep_ccinfos]
+    linking_contexts = [info.linking_context for info in dep_ccinfos]
+    toolchain = find_cpp_toolchain(ctx)
+    feature_configuration = cc_common.configure_features(
+        ctx = ctx,
+        cc_toolchain = toolchain,
+        requested_features = ctx.features,
+        unsupported_features = ctx.disabled_features,
+    )
+
+    (compilation_context, compilation_outputs) = cc_common.compile(
+        actions = ctx.actions,
+        feature_configuration = feature_configuration,
+        cc_toolchain = toolchain,
+        name = name,
+        srcs = srcs,
+        public_hdrs = hdrs,
+        user_compile_flags = copts,
+        compilation_contexts = compilation_contexts,
+    )
+
+    # buildifier: disable=unused-variable
+    (linking_context, linking_outputs) = cc_common.create_linking_context_from_compilation_outputs(
+        actions = ctx.actions,
+        name = name,
+        feature_configuration = feature_configuration,
+        cc_toolchain = toolchain,
+        compilation_outputs = compilation_outputs,
+        linking_contexts = linking_contexts,
+    )
+
+    return CcInfo(
+        compilation_context = compilation_context,
+        linking_context = linking_context,
+    )
+
+# Dummy rule to expose select() copts to aspects  ##############################
+
+UpbCcProtoLibraryCoptsInfo = provider(
+    "Provides copts for upb cc proto targets",
+    fields = {
+        "copts": "copts for upb_cc_proto_library()",
+    },
+)
+
+def upb_cc_proto_library_copts_impl(ctx):
+    return UpbCcProtoLibraryCoptsInfo(copts = ctx.attr.copts)
+
+upb_cc_proto_library_copts = rule(
+    implementation = upb_cc_proto_library_copts_impl,
+    attrs = {"copts": attr.string_list(default = [])},
+)
+
+_UpbCcWrappedCcInfo = provider("Provider for cc_info for protos", fields = ["cc_info"])
+_WrappedCcGeneratedSrcsInfo = provider("Provider for generated sources", fields = ["srcs"])
+
+def _compile_upb_cc_protos(ctx, generator, proto_info, proto_sources):
+    if len(proto_sources) == 0:
+        return GeneratedSrcsInfo(srcs = [], hdrs = [])
+
+    tool = getattr(ctx.executable, "_gen_" + generator)
+    srcs = [_generate_output_file(ctx, name, ".upb.proto.cc") for name in proto_sources]
+    hdrs = [_generate_output_file(ctx, name, ".upb.proto.h") for name in proto_sources]
+    hdrs += [_generate_output_file(ctx, name, ".upb.fwd.h") for name in proto_sources]
+    transitive_sets = proto_info.transitive_descriptor_sets.to_list()
+
+    args = ctx.actions.args()
+    args.use_param_file(param_file_arg = "@%s")
+    args.set_param_file_format("multiline")
+
+    args.add("--" + generator + "_out=" + _get_real_root(srcs[0]))
+    args.add("--plugin=protoc-gen-" + generator + "=" + tool.path)
+    args.add("--descriptor_set_in=" + ctx.configuration.host_path_separator.join([f.path for f in transitive_sets]))
+    args.add_all(proto_sources, map_each = _get_real_short_path)
+
+    ctx.actions.run(
+        inputs = depset(
+            direct = [proto_info.direct_descriptor_set],
+            transitive = [proto_info.transitive_descriptor_sets],
+        ),
+        tools = [tool],
+        outputs = srcs + hdrs,
+        executable = ctx.executable._protoc,
+        arguments = [args],
+        progress_message = "Generating upb cc protos for :" + ctx.label.name,
+    )
+    return GeneratedSrcsInfo(srcs = srcs, hdrs = hdrs)
+
+def _upb_cc_proto_rule_impl(ctx):
+    if len(ctx.attr.deps) != 1:
+        fail("only one deps dependency allowed.")
+    dep = ctx.attr.deps[0]
+
+    if _WrappedCcGeneratedSrcsInfo in dep:
+        srcs = dep[_WrappedCcGeneratedSrcsInfo].srcs
+    else:
+        fail("proto_library rule must generate _WrappedCcGeneratedSrcsInfo (aspect should have " +
+             "handled this).")
+
+    if _UpbCcWrappedCcInfo in dep:
+        cc_info = dep[_UpbCcWrappedCcInfo].cc_info
+    elif UpbWrappedCcInfo in dep:
+        cc_info = dep[UpbWrappedCcInfo].cc_info
+    else:
+        fail("proto_library rule must generate UpbWrappedCcInfo or " +
+             "_UpbCcWrappedCcInfo (aspect should have handled this).")
+
+    lib = cc_info.linking_context.linker_inputs.to_list()[0].libraries[0]
+    files = _filter_none([
+        lib.static_library,
+        lib.pic_static_library,
+        lib.dynamic_library,
+    ])
+    return [
+        DefaultInfo(files = depset(files + srcs.hdrs + srcs.srcs)),
+        srcs,
+        cc_info,
+    ]
+
+def _upb_cc_proto_aspect_impl(target, ctx, generator, cc_provider, file_provider):
+    proto_info = target[ProtoInfo]
+    files = _compile_upb_cc_protos(ctx, generator, proto_info, proto_info.direct_sources)
+    deps = ctx.rule.attr.deps + getattr(ctx.attr, "_" + generator)
+    dep_ccinfos = [dep[CcInfo] for dep in deps if CcInfo in dep]
+    dep_ccinfos += [dep[UpbWrappedCcInfo].cc_info for dep in deps if UpbWrappedCcInfo in dep]
+    dep_ccinfos += [dep[_UpbCcWrappedCcInfo].cc_info for dep in deps if _UpbCcWrappedCcInfo in dep]
+    if UpbWrappedCcInfo not in target:
+        fail("Target should have UpbWrappedCcInfo provider")
+    dep_ccinfos.append(target[UpbWrappedCcInfo].cc_info)
+    cc_info = _cc_library_func(
+        ctx = ctx,
+        name = ctx.rule.attr.name + "." + generator,
+        hdrs = files.hdrs,
+        srcs = files.srcs,
+        copts = ctx.attr._ccopts[UpbCcProtoLibraryCoptsInfo].copts,
+        dep_ccinfos = dep_ccinfos,
+    )
+    return [cc_provider(cc_info = cc_info), file_provider(srcs = files)]
+
+def _upb_cc_proto_library_aspect_impl(target, ctx):
+    return _upb_cc_proto_aspect_impl(target, ctx, "upbprotos", _UpbCcWrappedCcInfo, _WrappedCcGeneratedSrcsInfo)
+
+_upb_cc_proto_library_aspect = aspect(
+    attrs = {
+        "_ccopts": attr.label(
+            default = "//protos:upb_cc_proto_library_copts__for_generated_code_only_do_not_use",
+        ),
+        "_gen_upbprotos": attr.label(
+            executable = True,
+            cfg = "exec",
+            default = "//protos_generator:protoc-gen-upb-protos",
+        ),
+        "_protoc": attr.label(
+            executable = True,
+            cfg = "exec",
+            default = "//:protoc",
+        ),
+        "_cc_toolchain": attr.label(
+            default = "@bazel_tools//tools/cpp:current_cc_toolchain",
+        ),
+        "_upbprotos": attr.label_list(
+            default = [
+                # TODO: Add dependencies for cc runtime (absl/string etc..)
+                "//upb:generated_cpp_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+                "//protos:generated_protos_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me",
+                "@com_google_absl//absl/strings",
+                "@com_google_absl//absl/status:statusor",
+                "//protos",
+                "//protos:repeated_field",
+            ],
+        ),
+    },
+    implementation = _upb_cc_proto_library_aspect_impl,
+    provides = [
+        _UpbCcWrappedCcInfo,
+        _WrappedCcGeneratedSrcsInfo,
+    ],
+    required_aspect_providers = [
+        UpbWrappedCcInfo,
+    ],
+    attr_aspects = ["deps"],
+    fragments = ["cpp"],
+    toolchains = use_cpp_toolchain(),
+    incompatible_use_toolchain_transition = True,
+)
+
+upb_cc_proto_library = rule(
+    output_to_genfiles = True,
+    implementation = _upb_cc_proto_rule_impl,
+    attrs = {
+        "deps": attr.label_list(
+            aspects = [
+                upb_proto_library_aspect,
+                _upb_cc_proto_library_aspect,
+            ],
+            allow_rules = ["proto_library"],
+            providers = [ProtoInfo],
+        ),
+        "_ccopts": attr.label(
+            default = "//protos:upb_cc_proto_library_copts__for_generated_code_only_do_not_use",
+        ),
+    },
+)
diff --git a/protos/protos.cc b/protos/protos.cc
new file mode 100644
index 0000000..29b9fbe
--- /dev/null
+++ b/protos/protos.cc
@@ -0,0 +1,221 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protos/protos.h"
+
+#include <atomic>
+#include <cstddef>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "absl/strings/str_format.h"
+#include "absl/strings/string_view.h"
+#include "protos/protos_extension_lock.h"
+#include "upb/mem/arena.h"
+#include "upb/message/copy.h"
+#include "upb/message/internal/extension.h"
+#include "upb/message/promote.h"
+#include "upb/message/types.h"
+#include "upb/mini_table/extension.h"
+#include "upb/mini_table/extension_registry.h"
+#include "upb/mini_table/message.h"
+#include "upb/wire/decode.h"
+#include "upb/wire/encode.h"
+
+namespace protos {
+
+// begin:google_only
+// absl::Status MessageAllocationError(SourceLocation loc) {
+//   return absl::Status(absl::StatusCode::kInternal,
+//                       "Upb message allocation error", loc);
+// }
+//
+// absl::Status ExtensionNotFoundError(int extension_number, SourceLocation loc) {
+//   return absl::Status(
+//       absl::StatusCode::kInternal,
+//       absl::StrFormat("Extension %d not found", extension_number), loc);
+// }
+//
+// absl::Status MessageEncodeError(upb_EncodeStatus status, SourceLocation loc) {
+//   return absl::Status(absl::StatusCode::kInternal,
+//                       absl::StrFormat("Upb message encoding error %d", status),
+//                       loc
+//
+//   );
+// }
+//
+// absl::Status MessageDecodeError(upb_DecodeStatus status, SourceLocation loc
+//
+// ) {
+//   return absl::Status(absl::StatusCode::kInternal,
+//                       absl::StrFormat("Upb message parse error %d", status), loc
+//
+//   );
+// }
+// end:google_only
+
+// begin:github_only
+absl::Status MessageAllocationError(SourceLocation loc) {
+  return absl::Status(absl::StatusCode::kUnknown,
+                      "Upb message allocation error");
+}
+
+absl::Status ExtensionNotFoundError(int ext_number, SourceLocation loc) {
+  return absl::Status(absl::StatusCode::kUnknown,
+                      absl::StrFormat("Extension %d not found", ext_number));
+}
+
+absl::Status MessageEncodeError(upb_EncodeStatus s, SourceLocation loc) {
+  return absl::Status(absl::StatusCode::kUnknown, "Encoding error");
+}
+
+absl::Status MessageDecodeError(upb_DecodeStatus status, SourceLocation loc
+
+) {
+  return absl::Status(absl::StatusCode::kUnknown, "Upb message parse error");
+}
+// end:github_only
+
+namespace internal {
+
+upb_ExtensionRegistry* GetUpbExtensions(
+    const ExtensionRegistry& extension_registry) {
+  return extension_registry.registry_;
+}
+
+/**
+ * MessageLock(msg) acquires lock on msg when constructed and releases it when
+ * destroyed.
+ */
+class MessageLock {
+ public:
+  explicit MessageLock(const upb_Message* msg) : msg_(msg) {
+    UpbExtensionLocker locker =
+        upb_extension_locker_global.load(std::memory_order_acquire);
+    unlocker_ = (locker != nullptr) ? locker(msg) : nullptr;
+  }
+  MessageLock(const MessageLock&) = delete;
+  void operator=(const MessageLock&) = delete;
+  ~MessageLock() {
+    if (unlocker_ != nullptr) {
+      unlocker_(msg_);
+    }
+  }
+
+ private:
+  const upb_Message* msg_;
+  UpbExtensionUnlocker unlocker_;
+};
+
+bool HasExtensionOrUnknown(const upb_Message* msg,
+                           const upb_MiniTableExtension* eid) {
+  MessageLock msg_lock(msg);
+  return _upb_Message_Getext(msg, eid) != nullptr ||
+         upb_MiniTable_FindUnknown(msg, eid->field.number, 0).status ==
+             kUpb_FindUnknown_Ok;
+}
+
+const upb_Message_Extension* GetOrPromoteExtension(
+    upb_Message* msg, const upb_MiniTableExtension* eid, upb_Arena* arena) {
+  MessageLock msg_lock(msg);
+  const upb_Message_Extension* ext = _upb_Message_Getext(msg, eid);
+  if (ext == nullptr) {
+    upb_GetExtension_Status ext_status = upb_MiniTable_GetOrPromoteExtension(
+        (upb_Message*)msg, eid, 0, arena, &ext);
+    if (ext_status != kUpb_GetExtension_Ok) {
+      ext = nullptr;
+    }
+  }
+  return ext;
+}
+
+absl::StatusOr<absl::string_view> Serialize(const upb_Message* message,
+                                            const upb_MiniTable* mini_table,
+                                            upb_Arena* arena, int options) {
+  MessageLock msg_lock(message);
+  size_t len;
+  char* ptr;
+  upb_EncodeStatus status =
+      upb_Encode(message, mini_table, options, arena, &ptr, &len);
+  if (status == kUpb_EncodeStatus_Ok) {
+    return absl::string_view(ptr, len);
+  }
+  return MessageEncodeError(status);
+}
+
+void DeepCopy(upb_Message* target, const upb_Message* source,
+              const upb_MiniTable* mini_table, upb_Arena* arena) {
+  MessageLock msg_lock(source);
+  upb_Message_DeepCopy(target, source, mini_table, arena);
+}
+
+upb_Message* DeepClone(const upb_Message* source,
+                       const upb_MiniTable* mini_table, upb_Arena* arena) {
+  MessageLock msg_lock(source);
+  return upb_Message_DeepClone(source, mini_table, arena);
+}
+
+absl::Status MoveExtension(upb_Message* message, upb_Arena* message_arena,
+                           const upb_MiniTableExtension* ext,
+                           upb_Message* extension, upb_Arena* extension_arena) {
+  upb_Message_Extension* msg_ext =
+      _upb_Message_GetOrCreateExtension(message, ext, message_arena);
+  if (!msg_ext) {
+    return MessageAllocationError();
+  }
+  if (message_arena != extension_arena) {
+    // Try fuse, if fusing is not allowed or fails, create copy of extension.
+    if (!upb_Arena_Fuse(message_arena, extension_arena)) {
+      msg_ext->data.ptr =
+          DeepClone(extension, msg_ext->ext->sub.submsg, message_arena);
+      return absl::OkStatus();
+    }
+  }
+  msg_ext->data.ptr = extension;
+  return absl::OkStatus();
+}
+
+absl::Status SetExtension(upb_Message* message, upb_Arena* message_arena,
+                          const upb_MiniTableExtension* ext,
+                          const upb_Message* extension) {
+  upb_Message_Extension* msg_ext =
+      _upb_Message_GetOrCreateExtension(message, ext, message_arena);
+  if (!msg_ext) {
+    return MessageAllocationError();
+  }
+  // Clone extension into target message arena.
+  msg_ext->data.ptr =
+      DeepClone(extension, msg_ext->ext->sub.submsg, message_arena);
+  return absl::OkStatus();
+}
+
+}  // namespace internal
+
+}  // namespace protos
diff --git a/protos/protos.h b/protos/protos.h
new file mode 100644
index 0000000..195d793
--- /dev/null
+++ b/protos/protos.h
@@ -0,0 +1,551 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef UPB_PROTOS_PROTOS_H_
+#define UPB_PROTOS_PROTOS_H_
+
+#include <type_traits>
+#include <vector>
+
+#include "absl/status/status.h"
+#include "absl/status/statusor.h"
+#include "upb/base/status.hpp"
+#include "upb/mem/arena.hpp"
+#include "upb/message/copy.h"
+#include "upb/message/internal/accessors.h"
+#include "upb/message/internal/extension.h"
+#include "upb/wire/decode.h"
+#include "upb/wire/encode.h"
+
+namespace protos {
+
+using Arena = ::upb::Arena;
+class ExtensionRegistry;
+
+template <typename T>
+using Proxy = std::conditional_t<std::is_const<T>::value,
+                                 typename std::remove_const_t<T>::CProxy,
+                                 typename T::Proxy>;
+
+// Provides convenient access to Proxy and CProxy message types.
+//
+// Using rebinding and handling of const, Ptr<Message> and Ptr<const Message>
+// allows copying const with T* const and avoids using non-copyable Proxy types
+// directly.
+template <typename T>
+class Ptr final {
+ public:
+  Ptr() = delete;
+
+  // Implicit conversions
+  Ptr(T* m) : p_(m) {}                // NOLINT
+  Ptr(const Proxy<T>* p) : p_(*p) {}  // NOLINT
+  Ptr(Proxy<T> p) : p_(p) {}          // NOLINT
+  Ptr(const Ptr& m) = default;
+
+  Ptr& operator=(Ptr v) & {
+    Proxy<T>::Rebind(p_, v.p_);
+    return *this;
+  }
+
+  Proxy<T> operator*() const { return p_; }
+  Proxy<T>* operator->() const {
+    return const_cast<Proxy<T>*>(std::addressof(p_));
+  }
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wclass-conversion"
+#endif
+  template <typename U = T, std::enable_if_t<!std::is_const<U>::value, int> = 0>
+  operator Ptr<const T>() const {
+    Proxy<const T> p(p_);
+    return Ptr<const T>(&p);
+  }
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+ private:
+  Ptr(void* msg, upb_Arena* arena) : p_(msg, arena) {}  // NOLINT
+
+  friend class Ptr<const T>;
+  friend typename T::Access;
+
+  Proxy<T> p_;
+};
+
+inline absl::string_view UpbStrToStringView(upb_StringView str) {
+  return absl::string_view(str.data, str.size);
+}
+
+// TODO: update bzl and move to upb runtime / protos.cc.
+inline upb_StringView UpbStrFromStringView(absl::string_view str,
+                                           upb_Arena* arena) {
+  const size_t str_size = str.size();
+  char* buffer = static_cast<char*>(upb_Arena_Malloc(arena, str_size));
+  memcpy(buffer, str.data(), str_size);
+  return upb_StringView_FromDataAndSize(buffer, str_size);
+}
+
+template <typename T>
+typename T::Proxy CreateMessage(::protos::Arena& arena) {
+  return typename T::Proxy(upb_Message_New(T::minitable(), arena.ptr()),
+                           arena.ptr());
+}
+
+// begin:github_only
+// This type exists to work around an absl type that has not yet been
+// released.
+struct SourceLocation {
+  static SourceLocation current() { return {}; }
+  absl::string_view file_name() { return "<unknown>"; }
+  int line() { return 0; }
+};
+// end:github_only
+
+// begin:google_only
+// using SourceLocation = absl::SourceLocation;
+// end:google_only
+
+absl::Status MessageAllocationError(
+    SourceLocation loc = SourceLocation::current());
+
+absl::Status ExtensionNotFoundError(
+    int extension_number, SourceLocation loc = SourceLocation::current());
+
+absl::Status MessageDecodeError(upb_DecodeStatus status,
+                                SourceLocation loc = SourceLocation::current());
+
+absl::Status MessageEncodeError(upb_EncodeStatus status,
+                                SourceLocation loc = SourceLocation::current());
+
+namespace internal {
+struct PrivateAccess {
+  template <typename T>
+  static auto* GetInternalMsg(T&& message) {
+    return message->msg();
+  }
+  template <typename T>
+  static auto Proxy(void* p, upb_Arena* arena) {
+    return typename T::Proxy(p, arena);
+  }
+  template <typename T>
+  static auto CProxy(const void* p, upb_Arena* arena) {
+    return typename T::CProxy(p, arena);
+  }
+};
+
+template <typename T>
+auto* GetInternalMsg(T&& message) {
+  return PrivateAccess::GetInternalMsg(std::forward<T>(message));
+}
+
+template <typename T>
+T CreateMessage() {
+  return T();
+}
+
+template <typename T>
+typename T::Proxy CreateMessageProxy(void* msg, upb_Arena* arena) {
+  return typename T::Proxy(msg, arena);
+}
+
+template <typename T>
+typename T::CProxy CreateMessage(const upb_Message* msg, upb_Arena* arena) {
+  return PrivateAccess::CProxy<T>(msg, arena);
+}
+
+class ExtensionMiniTableProvider {
+ public:
+  constexpr explicit ExtensionMiniTableProvider(
+      const upb_MiniTableExtension* mini_table_ext)
+      : mini_table_ext_(mini_table_ext) {}
+  const upb_MiniTableExtension* mini_table_ext() const {
+    return mini_table_ext_;
+  }
+
+ private:
+  const upb_MiniTableExtension* mini_table_ext_;
+};
+
+// -------------------------------------------------------------------
+// ExtensionIdentifier
+// This is the type of actual extension objects.  E.g. if you have:
+//   extend Foo {
+//     optional MyExtension bar = 1234;
+//   }
+// then "bar" will be defined in C++ as:
+//   ExtensionIdentifier<Foo, MyExtension> bar(&namespace_bar_ext);
+template <typename ExtendeeType, typename ExtensionType>
+class ExtensionIdentifier : public ExtensionMiniTableProvider {
+ public:
+  using Extension = ExtensionType;
+  using Extendee = ExtendeeType;
+
+  constexpr explicit ExtensionIdentifier(
+      const upb_MiniTableExtension* mini_table_ext)
+      : ExtensionMiniTableProvider(mini_table_ext) {}
+};
+
+template <typename T>
+upb_Arena* GetArena(Ptr<T> message) {
+  return static_cast<upb_Arena*>(message->GetInternalArena());
+}
+
+template <typename T>
+upb_Arena* GetArena(T* message) {
+  return static_cast<upb_Arena*>(message->GetInternalArena());
+}
+
+template <typename T>
+const upb_MiniTable* GetMiniTable(const T*) {
+  return T::minitable();
+}
+
+template <typename T>
+const upb_MiniTable* GetMiniTable(Ptr<T>) {
+  return T::minitable();
+}
+
+upb_ExtensionRegistry* GetUpbExtensions(
+    const ExtensionRegistry& extension_registry);
+
+absl::StatusOr<absl::string_view> Serialize(const upb_Message* message,
+                                            const upb_MiniTable* mini_table,
+                                            upb_Arena* arena, int options);
+
+bool HasExtensionOrUnknown(const upb_Message* msg,
+                           const upb_MiniTableExtension* eid);
+
+const upb_Message_Extension* GetOrPromoteExtension(
+    upb_Message* msg, const upb_MiniTableExtension* eid, upb_Arena* arena);
+
+void DeepCopy(upb_Message* target, const upb_Message* source,
+              const upb_MiniTable* mini_table, upb_Arena* arena);
+
+upb_Message* DeepClone(const upb_Message* source,
+                       const upb_MiniTable* mini_table, upb_Arena* arena);
+
+absl::Status MoveExtension(upb_Message* message, upb_Arena* message_arena,
+                           const upb_MiniTableExtension* ext,
+                           upb_Message* extension, upb_Arena* extension_arena);
+
+absl::Status SetExtension(upb_Message* message, upb_Arena* message_arena,
+                          const upb_MiniTableExtension* ext,
+                          const upb_Message* extension);
+
+}  // namespace internal
+
+template <typename T>
+void DeepCopy(Ptr<const T> source_message, Ptr<T> target_message) {
+  static_assert(!std::is_const_v<T>);
+  ::protos::internal::DeepCopy(
+      internal::GetInternalMsg(target_message),
+      internal::GetInternalMsg(source_message), T::minitable(),
+      static_cast<upb_Arena*>(target_message->GetInternalArena()));
+}
+
+template <typename T>
+typename T::Proxy CloneMessage(Ptr<T> message, upb_Arena* arena) {
+  return internal::PrivateAccess::Proxy<T>(
+      ::protos::internal::DeepClone(internal::GetInternalMsg(message),
+                                    T::minitable(), arena),
+      arena);
+}
+
+template <typename T>
+void DeepCopy(Ptr<const T> source_message, T* target_message) {
+  static_assert(!std::is_const_v<T>);
+  DeepCopy(source_message, protos::Ptr(target_message));
+}
+
+template <typename T>
+void DeepCopy(const T* source_message, Ptr<T> target_message) {
+  static_assert(!std::is_const_v<T>);
+  DeepCopy(protos::Ptr(source_message), target_message);
+}
+
+template <typename T>
+void DeepCopy(const T* source_message, T* target_message) {
+  static_assert(!std::is_const_v<T>);
+  DeepCopy(protos::Ptr(source_message), protos::Ptr(target_message));
+}
+
+template <typename T>
+void ClearMessage(Ptr<T> message) {
+  static_assert(!std::is_const_v<T>, "");
+  upb_Message_Clear(internal::GetInternalMsg(message), T::minitable());
+}
+
+template <typename T>
+void ClearMessage(T* message) {
+  ClearMessage(protos::Ptr(message));
+}
+
+class ExtensionRegistry {
+ public:
+  ExtensionRegistry(
+      const std::vector<const ::protos::internal::ExtensionMiniTableProvider*>&
+          extensions,
+      const upb::Arena& arena)
+      : registry_(upb_ExtensionRegistry_New(arena.ptr())) {
+    if (registry_) {
+      for (const auto& ext_provider : extensions) {
+        const auto* ext = ext_provider->mini_table_ext();
+        bool success = upb_ExtensionRegistry_AddArray(registry_, &ext, 1);
+        if (!success) {
+          registry_ = nullptr;
+          break;
+        }
+      }
+    }
+  }
+
+ private:
+  friend upb_ExtensionRegistry* ::protos::internal::GetUpbExtensions(
+      const ExtensionRegistry& extension_registry);
+  upb_ExtensionRegistry* registry_;
+};
+
+template <typename T>
+using EnableIfProtosClass = std::enable_if_t<
+    std::is_base_of<typename T::Access, T>::value &&
+    std::is_base_of<typename T::Access, typename T::ExtendableType>::value>;
+
+template <typename T>
+using EnableIfMutableProto = std::enable_if_t<!std::is_const<T>::value>;
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+ABSL_MUST_USE_RESULT bool HasExtension(
+    Ptr<T> message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  return ::protos::internal::HasExtensionOrUnknown(
+      ::protos::internal::GetInternalMsg(message), id.mini_table_ext());
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+ABSL_MUST_USE_RESULT bool HasExtension(
+    const T* message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  return HasExtension(protos::Ptr(message), id);
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>>
+void ClearExtension(
+    Ptr<T> message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  static_assert(!std::is_const_v<T>, "");
+  _upb_Message_ClearExtensionField(internal::GetInternalMsg(message),
+                                   id.mini_table_ext());
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+void ClearExtension(
+    T* message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  ClearExtension(::protos::Ptr(message), id);
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>>
+absl::Status SetExtension(
+    Ptr<T> message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id,
+    const Extension& value) {
+  static_assert(!std::is_const_v<T>);
+  auto* message_arena = static_cast<upb_Arena*>(message->GetInternalArena());
+  return ::protos::internal::SetExtension(internal::GetInternalMsg(message),
+                                          message_arena, id.mini_table_ext(),
+                                          internal::GetInternalMsg(&value));
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>, typename = EnableIfMutableProto<T>>
+absl::Status SetExtension(
+    Ptr<T> message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id,
+    Extension&& value) {
+  Extension ext = std::move(value);
+  static_assert(!std::is_const_v<T>);
+  auto* message_arena = static_cast<upb_Arena*>(message->GetInternalArena());
+  auto* extension_arena = static_cast<upb_Arena*>(ext.GetInternalArena());
+  return ::protos::internal::MoveExtension(
+      internal::GetInternalMsg(message), message_arena, id.mini_table_ext(),
+      internal::GetInternalMsg(&ext), extension_arena);
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+absl::Status SetExtension(
+    T* message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id,
+    const Extension& value) {
+  return ::protos::SetExtension(::protos::Ptr(message), id, value);
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+absl::Status SetExtension(
+    T* message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id,
+    Extension&& value) {
+  return ::protos::SetExtension(::protos::Ptr(message), id,
+                                std::forward<Extension>(value));
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+absl::StatusOr<Ptr<const Extension>> GetExtension(
+    Ptr<T> message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  // TODO: Fix const correctness issues.
+  const upb_Message_Extension* ext = ::protos::internal::GetOrPromoteExtension(
+      const_cast<upb_Message*>(internal::GetInternalMsg(message)),
+      id.mini_table_ext(), ::protos::internal::GetArena(message));
+  if (!ext) {
+    return ExtensionNotFoundError(id.mini_table_ext()->field.number);
+  }
+  return Ptr<const Extension>(::protos::internal::CreateMessage<Extension>(
+      ext->data.ptr, ::protos::internal::GetArena(message)));
+}
+
+template <typename T, typename Extendee, typename Extension,
+          typename = EnableIfProtosClass<T>>
+absl::StatusOr<Ptr<const Extension>> GetExtension(
+    const T* message,
+    const ::protos::internal::ExtensionIdentifier<Extendee, Extension>& id) {
+  return GetExtension(protos::Ptr(message), id);
+}
+
+template <typename T>
+ABSL_MUST_USE_RESULT bool Parse(Ptr<T> message, absl::string_view bytes) {
+  static_assert(!std::is_const_v<T>);
+  upb_Message_Clear(internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message));
+  auto* arena = static_cast<upb_Arena*>(message->GetInternalArena());
+  return upb_Decode(bytes.data(), bytes.size(),
+                    internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message),
+                    /* extreg= */ nullptr, /* options= */ 0,
+                    arena) == kUpb_DecodeStatus_Ok;
+}
+
+template <typename T>
+ABSL_MUST_USE_RESULT bool Parse(
+    Ptr<T> message, absl::string_view bytes,
+    const ::protos::ExtensionRegistry& extension_registry) {
+  static_assert(!std::is_const_v<T>);
+  upb_Message_Clear(internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message));
+  auto* arena = static_cast<upb_Arena*>(message->GetInternalArena());
+  return upb_Decode(bytes.data(), bytes.size(),
+                    internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message),
+                    /* extreg= */
+                    ::protos::internal::GetUpbExtensions(extension_registry),
+                    /* options= */ 0, arena) == kUpb_DecodeStatus_Ok;
+}
+
+template <typename T>
+ABSL_MUST_USE_RESULT bool Parse(
+    T* message, absl::string_view bytes,
+    const ::protos::ExtensionRegistry& extension_registry) {
+  static_assert(!std::is_const_v<T>);
+  return Parse(protos::Ptr(message, bytes, extension_registry));
+}
+
+template <typename T>
+ABSL_MUST_USE_RESULT bool Parse(T* message, absl::string_view bytes) {
+  static_assert(!std::is_const_v<T>);
+  upb_Message_Clear(internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message));
+  auto* arena = static_cast<upb_Arena*>(message->GetInternalArena());
+  return upb_Decode(bytes.data(), bytes.size(),
+                    internal::GetInternalMsg(message),
+                    ::protos::internal::GetMiniTable(message),
+                    /* extreg= */ nullptr, /* options= */ 0,
+                    arena) == kUpb_DecodeStatus_Ok;
+}
+
+template <typename T>
+absl::StatusOr<T> Parse(absl::string_view bytes, int options = 0) {
+  T message;
+  auto* arena = static_cast<upb_Arena*>(message.GetInternalArena());
+  upb_DecodeStatus status =
+      upb_Decode(bytes.data(), bytes.size(), message.msg(),
+                 ::protos::internal::GetMiniTable(&message),
+                 /* extreg= */ nullptr, /* options= */ 0, arena);
+  if (status == kUpb_DecodeStatus_Ok) {
+    return message;
+  }
+  return MessageDecodeError(status);
+}
+
+template <typename T>
+absl::StatusOr<T> Parse(absl::string_view bytes,
+                        const ::protos::ExtensionRegistry& extension_registry,
+                        int options = 0) {
+  T message;
+  auto* arena = static_cast<upb_Arena*>(message.GetInternalArena());
+  upb_DecodeStatus status =
+      upb_Decode(bytes.data(), bytes.size(), message.msg(),
+                 ::protos::internal::GetMiniTable(&message),
+                 ::protos::internal::GetUpbExtensions(extension_registry),
+                 /* options= */ 0, arena);
+  if (status == kUpb_DecodeStatus_Ok) {
+    return message;
+  }
+  return MessageDecodeError(status);
+}
+
+template <typename T>
+absl::StatusOr<absl::string_view> Serialize(const T* message, upb::Arena& arena,
+                                            int options = 0) {
+  return ::protos::internal::Serialize(
+      internal::GetInternalMsg(message),
+      ::protos::internal::GetMiniTable(message), arena.ptr(), options);
+}
+
+template <typename T>
+absl::StatusOr<absl::string_view> Serialize(Ptr<T> message, upb::Arena& arena,
+                                            int options = 0) {
+  return ::protos::internal::Serialize(
+      internal::GetInternalMsg(message),
+      ::protos::internal::GetMiniTable(message), arena.ptr(), options);
+}
+
+}  // namespace protos
+
+#endif  // UPB_PROTOS_PROTOS_H_
diff --git a/protos/protos_extension_lock.cc b/protos/protos_extension_lock.cc
new file mode 100644
index 0000000..dbb2fc2
--- /dev/null
+++ b/protos/protos_extension_lock.cc
@@ -0,0 +1,39 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protos/protos_extension_lock.h"
+
+#include <atomic>
+
+namespace protos::internal {
+
+std::atomic<UpbExtensionLocker> upb_extension_locker_global;
+
+}  // namespace protos::internal
diff --git a/protos/protos_extension_lock.h b/protos/protos_extension_lock.h
new file mode 100644
index 0000000..36d66f1
--- /dev/null
+++ b/protos/protos_extension_lock.h
@@ -0,0 +1,54 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_
+#define UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_
+
+#include <atomic>
+
+namespace protos::internal {
+
+// TODO: Temporary locking api for cross-language
+// concurrency issue around extension api that uses lazy promotion
+// from unknown data to upb_MiniTableExtension. Will be replaced by
+// a core runtime solution in the future.
+//
+// Any api(s) using unknown or extension data (GetOrPromoteExtension,
+// Serialize and others) call lock/unlock to provide a way for
+// mixed language implementations to avoid race conditions)
+using UpbExtensionUnlocker = void (*)(const void*);
+using UpbExtensionLocker = UpbExtensionUnlocker (*)(const void*);
+
+// TODO: Expose as function instead of global.
+extern std::atomic<UpbExtensionLocker> upb_extension_locker_global;
+
+}  // namespace protos::internal
+
+#endif  // UPB_PROTOS_PROTOS_EXTENSION_LOCK_H_
diff --git a/protos/protos_extension_lock_test.cc b/protos/protos_extension_lock_test.cc
new file mode 100644
index 0000000..a0ce399
--- /dev/null
+++ b/protos/protos_extension_lock_test.cc
@@ -0,0 +1,147 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protos/protos_extension_lock.h"
+
+#include <atomic>
+#include <mutex>
+#include <string>
+#include <thread>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "absl/hash/hash.h"
+#include "absl/log/absl_check.h"
+#include "protos/protos.h"
+#include "protos_generator/tests/test_model.upb.proto.h"
+#include "upb/mem/arena.hpp"
+
+#ifndef ASSERT_OK
+#define ASSERT_OK(x) ASSERT_TRUE(x.ok())
+#endif  // ASSERT_OK
+#ifndef EXPECT_OK
+#define EXPECT_OK(x) EXPECT_TRUE(x.ok())
+#endif  // EXPECT_OK
+
+namespace protos_generator::test::protos {
+
+namespace {
+
+std::string GenerateTestData() {
+  TestModel model;
+  model.set_str1("str");
+  ThemeExtension extension1;
+  extension1.set_ext_name("theme");
+  ABSL_CHECK_OK(::protos::SetExtension(&model, theme, extension1));
+  ThemeExtension extension2;
+  extension2.set_ext_name("theme_extension");
+  ABSL_CHECK_OK(::protos::SetExtension(&model, ThemeExtension::theme_extension,
+                                       extension2));
+  ::upb::Arena arena;
+  auto bytes = ::protos::Serialize(&model, arena);
+  ABSL_CHECK_OK(bytes);
+  return std::string(bytes->data(), bytes->size());
+}
+
+std::mutex m[8];
+void unlock_func(const void* msg) { m[absl::HashOf(msg) & 0x7].unlock(); }
+::protos::internal::UpbExtensionUnlocker lock_func(const void* msg) {
+  m[absl::HashOf(msg) & 0x7].lock();
+  return &unlock_func;
+}
+
+void TestConcurrentExtensionAccess(::protos::ExtensionRegistry registry) {
+  ::protos::internal::upb_extension_locker_global.store(
+      &lock_func, std::memory_order_release);
+  const std::string payload = GenerateTestData();
+  TestModel parsed_model =
+      ::protos::Parse<TestModel>(payload, registry).value();
+  const auto test_main = [&] { EXPECT_EQ("str", parsed_model.str1()); };
+  const auto test_theme = [&] {
+    ASSERT_TRUE(::protos::HasExtension(&parsed_model, theme));
+    auto ext = ::protos::GetExtension(&parsed_model, theme);
+    ASSERT_OK(ext);
+    EXPECT_EQ((*ext)->ext_name(), "theme");
+  };
+  const auto test_theme_extension = [&] {
+    auto ext =
+        ::protos::GetExtension(&parsed_model, ThemeExtension::theme_extension);
+    ASSERT_OK(ext);
+    EXPECT_EQ((*ext)->ext_name(), "theme_extension");
+  };
+  const auto test_serialize = [&] {
+    ::upb::Arena arena;
+    EXPECT_OK(::protos::Serialize(&parsed_model, arena));
+  };
+  const auto test_copy_constructor = [&] {
+    TestModel copy_a = parsed_model;
+    TestModel copy_b = parsed_model;
+    EXPECT_EQ(copy_a.has_str1(), copy_b.has_str1());
+  };
+  std::thread t1(test_main);
+  std::thread t2(test_main);
+  std::thread t3(test_theme);
+  std::thread t4(test_theme);
+  std::thread t5(test_theme_extension);
+  std::thread t6(test_theme_extension);
+  std::thread t7(test_serialize);
+  std::thread t8(test_copy_constructor);
+  t1.join();
+  t2.join();
+  t3.join();
+  t4.join();
+  t5.join();
+  t6.join();
+  t7.join();
+  t8.join();
+  test_main();
+  test_theme();
+  test_theme_extension();
+}
+
+TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceBothLazy) {
+  ::upb::Arena arena;
+  TestConcurrentExtensionAccess({{}, arena});
+}
+
+TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceOneLazyOneEager) {
+  ::upb::Arena arena;
+  TestConcurrentExtensionAccess({{&theme}, arena});
+  TestConcurrentExtensionAccess({{&ThemeExtension::theme_extension}, arena});
+}
+
+TEST(CppGeneratedCode, ConcurrentAccessDoesNotRaceBothEager) {
+  ::upb::Arena arena;
+  TestConcurrentExtensionAccess(
+      {{&theme, &ThemeExtension::theme_extension}, arena});
+}
+
+}  // namespace
+}  // namespace protos_generator::test::protos
diff --git a/protos/protos_internal.h b/protos/protos_internal.h
new file mode 100644
index 0000000..0a1f194
--- /dev/null
+++ b/protos/protos_internal.h
@@ -0,0 +1,48 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef UPB_PROTOS_PROTOS_INTERNAL_H_
+#define UPB_PROTOS_PROTOS_INTERNAL_H_
+
+#include "upb/mem/arena.h"
+#include "upb/message/message.h"
+
+namespace protos::internal {
+
+// Moves ownership of a message created in a source arena.
+//
+// Utility function to provide a way to move ownership across languages or VMs.
+template <typename T>
+T MoveMessage(upb_Message* msg, upb_Arena* arena) {
+  return T(msg, arena);
+}
+
+}  // namespace protos::internal
+#endif
diff --git a/protos/protos_internal_test.cc b/protos/protos_internal_test.cc
new file mode 100644
index 0000000..a173665
--- /dev/null
+++ b/protos/protos_internal_test.cc
@@ -0,0 +1,60 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protos/protos_internal.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include "protos_generator/tests/test_model.upb.h"
+#include "protos_generator/tests/test_model.upb.proto.h"
+#include "upb/mem/arena.h"
+
+namespace protos::testing {
+namespace {
+using ::protos_generator::test::protos::TestModel;
+
+TEST(CppGeneratedCode, InternalMoveMessage) {
+  // Generate message (simulating message created in another VM/language)
+  upb_Arena* source_arena = upb_Arena_New();
+  protos_generator_test_TestModel* message =
+      protos_generator_test_TestModel_new(source_arena);
+  ASSERT_NE(message, nullptr);
+  protos_generator_test_TestModel_set_int_value_with_default(message, 123);
+
+  // Move ownership.
+  TestModel model =
+      protos::internal::MoveMessage<TestModel>(message, source_arena);
+  // Now that we have moved ownership, free original arena.
+  upb_Arena_Free(source_arena);
+  EXPECT_EQ(model.int_value_with_default(), 123);
+}
+
+}  // namespace
+}  // namespace protos::testing
diff --git a/protos/protos_traits.h b/protos/protos_traits.h
new file mode 100644
index 0000000..92b9dc3
--- /dev/null
+++ b/protos/protos_traits.h
@@ -0,0 +1,44 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_
+#define THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_
+
+#include <type_traits>
+
+namespace protos::internal {
+
+template <typename T, typename T2>
+using add_const_if_T_is_const =
+    std::conditional_t<std::is_const_v<T>, const T2, T2>;
+
+}  // namespace protos::internal
+
+#endif  // THIRD_PARTY_UPB_PROTOS_PROTOS_TRAITS_H_
diff --git a/protos/repeated_field.h b/protos/repeated_field.h
new file mode 100644
index 0000000..6edcfb5
--- /dev/null
+++ b/protos/repeated_field.h
@@ -0,0 +1,325 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#ifndef UPB_PROTOS_REPEATED_FIELD_H_
+#define UPB_PROTOS_REPEATED_FIELD_H_
+
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+#include "absl/strings/string_view.h"
+#include "protos/protos.h"
+#include "protos/protos_traits.h"
+#include "protos/repeated_field_iterator.h"
+#include "upb/base/string_view.h"
+#include "upb/collections/array.h"
+#include "upb/mem/arena.h"
+#include "upb/message/copy.h"
+#include "upb/message/types.h"
+
+// Must be last:
+#include "upb/port/def.inc"
+
+namespace protos {
+
+namespace internal {
+
+// Shared implementation of repeated fields for absl::string_view and
+// message types for mutable and immutable variants.
+//
+// Immutable (const accessor), constructs this class with a nullptr upb_Array*
+// when the underlying array in the message is empty.
+//
+// Mutable accessors on the other hand, will allocate a new empty non-null
+// upb_Array* for the message when the RepeatedFieldProxy is constructed.
+template <class T>
+class RepeatedFieldProxyBase {
+  using Array = add_const_if_T_is_const<T, upb_Array>;
+
+ public:
+  explicit RepeatedFieldProxyBase(Array* arr, upb_Arena* arena)
+      : arr_(arr), arena_(arena) {}
+
+  size_t size() const { return arr_ != nullptr ? upb_Array_Size(arr_) : 0; }
+
+  bool empty() const { return size() == 0; }
+
+ protected:
+  // Returns upb_Array message member.
+  inline upb_Message* GetMessage(size_t n) const;
+
+  Array* arr_;
+  upb_Arena* arena_;
+};
+
+template <class T>
+upb_Message* RepeatedFieldProxyBase<T>::GetMessage(size_t n) const {
+  auto** messages =
+      static_cast<upb_Message**>(upb_Array_MutableDataPtr(this->arr_));
+  return messages[n];
+}
+
+template <class T>
+class RepeatedFieldProxyMutableBase : public RepeatedFieldProxyBase<T> {
+ public:
+  RepeatedFieldProxyMutableBase(upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyBase<T>(arr, arena) {}
+
+  void clear() { upb_Array_Resize(this->arr_, 0, this->arena_); }
+};
+
+// RepeatedField proxy for repeated messages.
+template <class T>
+class RepeatedFieldProxy
+    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
+                                RepeatedFieldProxyMutableBase<T>> {
+  static_assert(!std::is_same_v<T, absl::string_view>, "");
+  static_assert(!std::is_same_v<T, const absl::string_view>, "");
+  static_assert(!std::is_arithmetic_v<T>, "");
+  static constexpr bool kIsConst = std::is_const_v<T>;
+
+ public:
+  using value_type = std::remove_const_t<T>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using iterator = internal::Iterator<MessageIteratorPolicy<T>>;
+  using reference = typename iterator::reference;
+  using pointer = typename iterator::pointer;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+
+  explicit RepeatedFieldProxy(const upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyBase<T>(arr, arena) {}
+  RepeatedFieldProxy(upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
+  // Constructor used by ::protos::Ptr.
+  RepeatedFieldProxy(const RepeatedFieldProxy&) = default;
+
+  // T::CProxy [] operator specialization.
+  typename T::CProxy operator[](size_t n) const {
+    upb_MessageValue message_value = upb_Array_Get(this->arr_, n);
+    return ::protos::internal::CreateMessage<typename std::remove_const_t<T>>(
+        (upb_Message*)message_value.msg_val, this->arena_);
+  }
+
+  // TODO : Audit/Finalize based on Iterator Design.
+  // T::Proxy [] operator specialization.
+  template <int&... DeductionBlocker, bool b = !kIsConst,
+            typename = std::enable_if_t<b>>
+  typename T::Proxy operator[](size_t n) {
+    return ::protos::internal::CreateMessageProxy<T>(this->GetMessage(n),
+                                                     this->arena_);
+  }
+
+  // Mutable message reference specialization.
+  template <int&... DeductionBlocker, bool b = !kIsConst,
+            typename = std::enable_if_t<b>>
+  void push_back(const T& t) {
+    upb_MessageValue message_value;
+    message_value.msg_val = upb_Message_DeepClone(
+        PrivateAccess::GetInternalMsg(&t), ::protos::internal::GetMiniTable(&t),
+        this->arena_);
+    upb_Array_Append(this->arr_, message_value, this->arena_);
+  }
+
+  // Mutable message add using move.
+  template <int&... DeductionBlocker, bool b = !kIsConst,
+            typename = std::enable_if_t<b>>
+  void push_back(T&& msg) {
+    upb_MessageValue message_value;
+    message_value.msg_val = PrivateAccess::GetInternalMsg(&msg);
+    upb_Arena_Fuse(GetArena(&msg), this->arena_);
+    upb_Array_Append(this->arr_, message_value, this->arena_);
+    T moved_msg = std::move(msg);
+  }
+
+  iterator begin() const {
+    return iterator({static_cast<upb_Message**>(
+                         const_cast<void*>(upb_Array_DataPtr(this->arr_))),
+                     this->arena_});
+  }
+  iterator end() const { return begin() + this->size(); }
+  reverse_iterator rbegin() const { return reverse_iterator(end()); }
+  reverse_iterator rend() const { return reverse_iterator(begin()); }
+
+ private:
+  friend class ::protos::Ptr<T>;
+};
+
+// RepeatedField proxy for repeated strings.
+template <class T>
+class RepeatedFieldStringProxy
+    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
+                                RepeatedFieldProxyMutableBase<T>> {
+  static_assert(std::is_same_v<T, absl::string_view> ||
+                    std::is_same_v<T, const absl::string_view>,
+                "");
+  static constexpr bool kIsConst = std::is_const_v<T>;
+
+ public:
+  using value_type = std::remove_const_t<T>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using iterator = internal::Iterator<StringIteratorPolicy<T>>;
+  using reference = typename iterator::reference;
+  using pointer = typename iterator::pointer;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+
+  // Immutable constructor.
+  explicit RepeatedFieldStringProxy(const upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyBase<T>(arr, arena) {}
+  // Mutable constructor.
+  RepeatedFieldStringProxy(upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
+  // Constructor used by ::protos::Ptr.
+  RepeatedFieldStringProxy(const RepeatedFieldStringProxy&) = default;
+
+  reference operator[](size_t n) const { return begin()[n]; }
+
+  template <int&... DeductionBlocker, bool b = !kIsConst,
+            typename = std::enable_if_t<b>>
+  void push_back(T t) {
+    upb_MessageValue message_value;
+    // Copy string to arena.
+    UPB_ASSERT(this->arena_);
+    char* data = (char*)upb_Arena_Malloc(this->arena_, t.size());
+    UPB_ASSERT(data);
+    memcpy(data, t.data(), t.size());
+    message_value.str_val = upb_StringView_FromDataAndSize(data, t.size());
+    upb_Array_Append(this->arr_, message_value, this->arena_);
+  }
+
+  iterator begin() const { return iterator({this->arr_, this->arena_, 0}); }
+  iterator end() const {
+    return iterator({this->arr_, this->arena_, this->size()});
+  }
+  reverse_iterator rbegin() const { return reverse_iterator(end()); }
+  reverse_iterator rend() const { return reverse_iterator(begin()); }
+};
+
+// RepeatedField proxy for repeated scalar types.
+template <typename T>
+class RepeatedFieldScalarProxy
+    : public std::conditional_t<std::is_const_v<T>, RepeatedFieldProxyBase<T>,
+                                RepeatedFieldProxyMutableBase<T>> {
+  static_assert(std::is_arithmetic_v<T>, "");
+  static constexpr bool kIsConst = std::is_const_v<T>;
+
+ public:
+  using value_type = std::remove_const_t<T>;
+  using size_type = size_t;
+  using difference_type = ptrdiff_t;
+  using iterator = internal::Iterator<ScalarIteratorPolicy<T>>;
+  using reference = typename iterator::reference;
+  using pointer = typename iterator::pointer;
+  using reverse_iterator = std::reverse_iterator<iterator>;
+
+  explicit RepeatedFieldScalarProxy(const upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyBase<T>(arr, arena) {}
+  RepeatedFieldScalarProxy(upb_Array* arr, upb_Arena* arena)
+      : RepeatedFieldProxyMutableBase<T>(arr, arena) {}
+  // Constructor used by ::protos::Ptr.
+  RepeatedFieldScalarProxy(const RepeatedFieldScalarProxy&) = default;
+
+  T operator[](size_t n) const {
+    upb_MessageValue message_value = upb_Array_Get(this->arr_, n);
+    typename std::remove_const_t<T> val;
+    memcpy(&val, &message_value, sizeof(T));
+    return val;
+  }
+
+  template <int&... DeductionBlocker, bool b = !kIsConst,
+            typename = std::enable_if_t<b>>
+  void push_back(T t) {
+    upb_MessageValue message_value;
+    memcpy(&message_value, &t, sizeof(T));
+    upb_Array_Append(this->arr_, message_value, this->arena_);
+  }
+
+  iterator begin() const { return iterator({unsafe_array()}); }
+  iterator cbegin() const { return begin(); }
+  iterator end() const { return iterator({unsafe_array() + this->size()}); }
+  iterator cend() const { return end(); }
+
+  // Reverse iterator support.
+  reverse_iterator rbegin() const { return reverse_iterator(end()); }
+  reverse_iterator rend() const { return reverse_iterator(begin()); }
+  reverse_iterator crbegin() const { return reverse_iterator(end()); }
+  reverse_iterator crend() const { return reverse_iterator(begin()); }
+
+ private:
+  T* unsafe_array() const {
+    if (kIsConst) {
+      const void* unsafe_ptr = ::upb_Array_DataPtr(this->arr_);
+      return static_cast<T*>(const_cast<void*>(unsafe_ptr));
+    }
+    if (!kIsConst) {
+      void* unsafe_ptr =
+          ::upb_Array_MutableDataPtr(const_cast<upb_Array*>(this->arr_));
+      return static_cast<T*>(unsafe_ptr);
+    }
+  }
+};
+
+}  // namespace internal
+
+template <typename T>
+class RepeatedField {
+  static constexpr bool kIsString = std::is_same_v<T, absl::string_view>;
+  static constexpr bool kIsScalar = std::is_arithmetic_v<T>;
+
+ public:
+  using Proxy = std::conditional_t<
+      kIsScalar, internal::RepeatedFieldScalarProxy<T>,
+      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>,
+                         internal::RepeatedFieldProxy<T>>>;
+  using CProxy = std::conditional_t<
+      kIsScalar, internal::RepeatedFieldScalarProxy<const T>,
+      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<const T>,
+                         internal::RepeatedFieldProxy<const T>>>;
+  // TODO: T supports incomplete type from fwd.h forwarding headers
+  // We would like to reference T::CProxy. Validate forwarding header design.
+  using ValueProxy = std::conditional_t<
+      kIsScalar, T,
+      std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<T>>>;
+  using ValueCProxy = std::conditional_t<
+      kIsScalar, const T,
+      std::conditional_t<kIsString, absl::string_view, ::protos::Ptr<const T>>>;
+  using Access = std::conditional_t<
+      kIsScalar, internal::RepeatedFieldScalarProxy<T>,
+      std::conditional_t<kIsString, internal::RepeatedFieldStringProxy<T>,
+                         internal::RepeatedFieldProxy<T>>>;
+};
+
+}  // namespace protos
+
+#include "upb/port/undef.inc"
+
+#endif  // UPB_PROTOS_REPEATED_FIELD_H_
diff --git a/protos/repeated_field_iterator.h b/protos/repeated_field_iterator.h
new file mode 100644
index 0000000..a552d76
--- /dev/null
+++ b/protos/repeated_field_iterator.h
@@ -0,0 +1,396 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#ifndef UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_
+#define UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_
+
+#include <cstddef>
+#include <cstring>
+#include <iterator>
+#include <type_traits>
+
+#include "absl/strings/string_view.h"
+#include "protos/protos.h"
+#include "upb/base/string_view.h"
+#include "upb/collections/array.h"
+#include "upb/mem/arena.h"
+#include "upb/message/copy.h"
+
+// Must be last:
+#include "upb/message/types.h"
+#include "upb/port/def.inc"
+
+namespace protos {
+namespace internal {
+
+// TODO: Implement std iterator for messages
+template <typename T>
+class RepeatedFieldScalarProxy;
+template <typename T>
+class RepeatedFieldStringProxy;
+
+struct IteratorTestPeer;
+
+template <typename T>
+class Iterator;
+
+template <typename PolicyT>
+class ReferenceProxy;
+
+template <typename PolicyT>
+class InjectedRelationalsImpl {
+  using RP = ReferenceProxy<PolicyT>;
+  using V = typename PolicyT::value_type;
+  friend bool operator==(RP a, V b) { return static_cast<V>(a) == b; }
+  friend bool operator==(V a, RP b) { return a == static_cast<V>(b); }
+  friend bool operator==(RP a, RP b) {
+    return static_cast<V>(a) == static_cast<V>(b);
+  }
+  friend bool operator!=(RP a, V b) { return static_cast<V>(a) != b; }
+  friend bool operator!=(V a, RP b) { return a != static_cast<V>(b); }
+  friend bool operator!=(RP a, RP b) {
+    return static_cast<V>(a) != static_cast<V>(b);
+  }
+  friend bool operator<(RP a, V b) { return static_cast<V>(a) < b; }
+  friend bool operator<(V a, RP b) { return a < static_cast<V>(b); }
+  friend bool operator<(RP a, RP b) {
+    return static_cast<V>(a) < static_cast<V>(b);
+  }
+  friend bool operator<=(RP a, V b) { return static_cast<V>(a) <= b; }
+  friend bool operator<=(V a, RP b) { return a <= static_cast<V>(b); }
+  friend bool operator<=(RP a, RP b) {
+    return static_cast<V>(a) <= static_cast<V>(b);
+  }
+  friend bool operator>(RP a, V b) { return static_cast<V>(a) > b; }
+  friend bool operator>(V a, RP b) { return a > static_cast<V>(b); }
+  friend bool operator>(RP a, RP b) {
+    return static_cast<V>(a) > static_cast<V>(b);
+  }
+  friend bool operator>=(RP a, V b) { return static_cast<V>(a) >= b; }
+  friend bool operator>=(V a, RP b) { return a >= static_cast<V>(b); }
+  friend bool operator>=(RP a, RP b) {
+    return static_cast<V>(a) >= static_cast<V>(b);
+  }
+};
+class NoInjectedRelationalsImpl {};
+
+// We need to inject relationals for the string references because the
+// relationals for string_view are templates and won't allow for implicit
+// conversions from ReferenceProxy to string_view before deduction.
+template <typename PolicyT>
+using InjectedRelationals = std::conditional_t<
+    std::is_same_v<std::remove_const_t<typename PolicyT::value_type>,
+                   absl::string_view>,
+    InjectedRelationalsImpl<PolicyT>, NoInjectedRelationalsImpl>;
+
+template <typename PolicyT>
+class ReferenceProxy : InjectedRelationals<PolicyT> {
+  using value_type = typename PolicyT::value_type;
+
+ public:
+  ReferenceProxy(const ReferenceProxy&) = default;
+  ReferenceProxy& operator=(const ReferenceProxy& other) {
+    // Assign through the references
+    // TODO: Make this better for strings to avoid the copy.
+    it_.Set(other.it_.Get());
+    return *this;
+  }
+  friend void swap(ReferenceProxy a, ReferenceProxy b) { a.it_.swap(b.it_); }
+
+  operator value_type() const { return it_.Get(); }
+  void operator=(const value_type& value) const { it_.Set(value); }
+  void operator=(value_type&& value) const { it_.Set(std::move(value)); }
+  Iterator<PolicyT> operator&() const { return Iterator<PolicyT>(it_); }
+
+ private:
+  friend IteratorTestPeer;
+  friend ReferenceProxy<typename PolicyT::AddConst>;
+  friend Iterator<PolicyT>;
+
+  explicit ReferenceProxy(typename PolicyT::Payload elem) : it_(elem) {}
+  typename PolicyT::Payload it_;
+};
+
+template <template <typename> class PolicyTemplate, typename T>
+class ReferenceProxy<PolicyTemplate<const T>>
+    : InjectedRelationals<PolicyTemplate<const T>> {
+  using PolicyT = PolicyTemplate<const T>;
+  using value_type = typename PolicyT::value_type;
+
+ public:
+  ReferenceProxy(ReferenceProxy<PolicyTemplate<T>> p) : it_(p.it_) {}
+  ReferenceProxy(const ReferenceProxy&) = default;
+  ReferenceProxy& operator=(const ReferenceProxy&) = delete;
+
+  operator value_type() const { return it_.Get(); }
+  Iterator<PolicyT> operator&() const { return Iterator<PolicyT>(it_); }
+
+ private:
+  friend IteratorTestPeer;
+  friend Iterator<PolicyT>;
+
+  explicit ReferenceProxy(typename PolicyT::Payload elem) : it_(elem) {}
+  typename PolicyT::Payload it_;
+};
+
+template <typename PolicyT>
+class Iterator {
+ public:
+  using iterator_category = std::random_access_iterator_tag;
+  using value_type = std::remove_const_t<typename PolicyT::value_type>;
+  using difference_type = std::ptrdiff_t;
+  using pointer = Iterator;
+  using reference =
+      std::conditional_t<PolicyT::kUseReferenceProxy, ReferenceProxy<PolicyT>,
+                         typename PolicyT::value_type>;
+
+  constexpr Iterator() noexcept : it_(nullptr) {}
+  Iterator(const Iterator& other) = default;
+  Iterator& operator=(const Iterator& other) = default;
+  template <
+      typename P = PolicyT,
+      typename = std::enable_if_t<std::is_const<typename P::value_type>::value>>
+  Iterator(const Iterator<typename P::RemoveConst>& other) : it_(other.it_) {}
+
+  constexpr reference operator*() const noexcept {
+    if constexpr (PolicyT::kUseReferenceProxy) {
+      return reference(it_);
+    } else {
+      return it_.Get();
+    }
+  }
+  // No operator-> needed because T is a scalar.
+
+ private:
+  // Hide the internal type.
+  using iterator = Iterator;
+
+ public:
+  // {inc,dec}rementable
+  constexpr iterator& operator++() noexcept {
+    it_.AddOffset(1);
+    return *this;
+  }
+  constexpr iterator operator++(int) noexcept {
+    auto copy = *this;
+    ++*this;
+    return copy;
+  }
+  constexpr iterator& operator--() noexcept {
+    it_.AddOffset(-1);
+    return *this;
+  }
+  constexpr iterator operator--(int) noexcept {
+    auto copy = *this;
+    --*this;
+    return copy;
+  }
+
+  // equality_comparable
+  friend constexpr bool operator==(const iterator& x,
+                                   const iterator& y) noexcept {
+    return x.it_.Index() == y.it_.Index();
+  }
+  friend constexpr bool operator!=(const iterator& x,
+                                   const iterator& y) noexcept {
+    return !(x == y);
+  }
+
+  // less_than_comparable
+  friend constexpr bool operator<(const iterator& x,
+                                  const iterator& y) noexcept {
+    return x.it_.Index() < y.it_.Index();
+  }
+  friend constexpr bool operator<=(const iterator& x,
+                                   const iterator& y) noexcept {
+    return !(y < x);
+  }
+  friend constexpr bool operator>(const iterator& x,
+                                  const iterator& y) noexcept {
+    return y < x;
+  }
+  friend constexpr bool operator>=(const iterator& x,
+                                   const iterator& y) noexcept {
+    return !(x < y);
+  }
+
+  constexpr iterator& operator+=(difference_type d) noexcept {
+    it_.AddOffset(d);
+    return *this;
+  }
+  constexpr iterator operator+(difference_type d) const noexcept {
+    auto copy = *this;
+    copy += d;
+    return copy;
+  }
+  friend constexpr iterator operator+(const difference_type d,
+                                      iterator it) noexcept {
+    return it + d;
+  }
+
+  constexpr iterator& operator-=(difference_type d) noexcept {
+    it_.AddOffset(-d);
+    return *this;
+  }
+  constexpr iterator operator-(difference_type d) const noexcept {
+    auto copy = *this;
+    copy -= d;
+    return copy;
+  }
+
+  // indexable
+  constexpr reference operator[](difference_type d) const noexcept {
+    auto copy = *this;
+    copy += d;
+    return *copy;
+  }
+
+  // random access iterator
+  friend constexpr difference_type operator-(iterator x, iterator y) noexcept {
+    return x.it_.Index() - y.it_.Index();
+  }
+
+ private:
+  friend IteratorTestPeer;
+  friend ReferenceProxy<PolicyT>;
+  friend Iterator<typename PolicyT::AddConst>;
+  template <typename U>
+  friend class RepeatedFieldScalarProxy;
+  template <typename U>
+  friend class RepeatedFieldStringProxy;
+  template <typename U>
+  friend class RepeatedFieldProxy;
+
+  // Create from internal::RepeatedFieldScalarProxy.
+  explicit Iterator(typename PolicyT::Payload it) noexcept : it_(it) {}
+
+  // The internal iterator.
+  typename PolicyT::Payload it_;
+};
+
+template <typename T>
+struct ScalarIteratorPolicy {
+  static constexpr bool kUseReferenceProxy = true;
+  using value_type = T;
+  using RemoveConst = ScalarIteratorPolicy<std::remove_const_t<T>>;
+  using AddConst = ScalarIteratorPolicy<const T>;
+
+  struct Payload {
+    T* value;
+    void AddOffset(ptrdiff_t offset) { value += offset; }
+    T Get() const { return *value; }
+    void Set(T new_value) const { *value = new_value; }
+    T* Index() const { return value; }
+
+    void swap(Payload& other) {
+      using std::swap;
+      swap(*value, *other.value);
+    }
+
+    operator typename ScalarIteratorPolicy<const T>::Payload() const {
+      return {value};
+    }
+  };
+};
+
+template <typename T>
+struct StringIteratorPolicy {
+  static constexpr bool kUseReferenceProxy = true;
+  using value_type = T;
+  using RemoveConst = StringIteratorPolicy<std::remove_const_t<T>>;
+  using AddConst = StringIteratorPolicy<const T>;
+
+  struct Payload {
+    using Array =
+        std::conditional_t<std::is_const_v<T>, const upb_Array, upb_Array>;
+    Array* arr;
+    upb_Arena* arena;
+    size_t index;
+
+    void AddOffset(ptrdiff_t offset) { index += offset; }
+    absl::string_view Get() const {
+      upb_MessageValue message_value = upb_Array_Get(arr, index);
+      return absl::string_view(message_value.str_val.data,
+                               message_value.str_val.size);
+    }
+    void Set(absl::string_view new_value) const {
+      char* data =
+          static_cast<char*>(upb_Arena_Malloc(arena, new_value.size()));
+      memcpy(data, new_value.data(), new_value.size());
+      upb_MessageValue message_value;
+      message_value.str_val =
+          upb_StringView_FromDataAndSize(data, new_value.size());
+      upb_Array_Set(arr, index, message_value);
+    }
+    size_t Index() const { return index; }
+
+    void swap(Payload& other) {
+      upb_MessageValue a = upb_Array_Get(this->arr, this->index);
+      upb_MessageValue b = upb_Array_Get(other.arr, other.index);
+      upb_Array_Set(this->arr, this->index, b);
+      upb_Array_Set(other.arr, other.index, a);
+    }
+
+    operator typename StringIteratorPolicy<const T>::Payload() const {
+      return {arr, arena, index};
+    }
+  };
+};
+
+template <typename T>
+struct MessageIteratorPolicy {
+  static constexpr bool kUseReferenceProxy = false;
+  using value_type = std::conditional_t<std::is_const_v<T>, typename T::CProxy,
+                                        typename T::Proxy>;
+  using RemoveConst = MessageIteratorPolicy<std::remove_const_t<T>>;
+  using AddConst = MessageIteratorPolicy<const T>;
+
+  struct Payload {
+    using Array =
+        std::conditional_t<std::is_const_v<T>, const upb_Array, upb_Array>;
+    upb_Message** arr;
+    upb_Arena* arena;
+
+    void AddOffset(ptrdiff_t offset) { arr += offset; }
+    auto Get() const {
+      if constexpr (std::is_const_v<T>) {
+        return ::protos::internal::CreateMessage<
+            typename std::remove_const_t<T>>(*arr, arena);
+      } else {
+        return ::protos::internal::CreateMessageProxy<T>(*arr, arena);
+      }
+    }
+    auto Index() const { return arr; }
+  };
+};
+
+}  // namespace internal
+}  // namespace protos
+
+#endif  // UPB_PROTOS_REPEATED_FIELD_ITERATOR_H_
diff --git a/protos/repeated_field_iterator_test.cc b/protos/repeated_field_iterator_test.cc
new file mode 100644
index 0000000..a8de900
--- /dev/null
+++ b/protos/repeated_field_iterator_test.cc
@@ -0,0 +1,478 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 Google LLC.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google LLC nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "protos/repeated_field_iterator.h"
+
+#include <algorithm>
+#include <array>
+#include <numeric>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using ::testing::ElementsAre;
+
+namespace protos {
+namespace internal {
+
+template <typename T>
+using ScalarRef = ReferenceProxy<ScalarIteratorPolicy<T>>;
+template <typename T>
+using ScalarIterator = Iterator<ScalarIteratorPolicy<T>>;
+
+template <typename T>
+using StringRef = ReferenceProxy<StringIteratorPolicy<T>>;
+template <typename T>
+using StringIterator = Iterator<StringIteratorPolicy<T>>;
+
+struct IteratorTestPeer {
+  template <typename T>
+  static ScalarRef<T> MakeScalarRefProxy(T& ref) {
+    return ScalarRef<T>({&ref});
+  }
+
+  template <typename T>
+  static ScalarIterator<T> MakeScalarIterator(T* ptr) {
+    return ScalarIterator<T>({ptr});
+  }
+
+  template <typename T>
+  static StringRef<T> MakeStringRefProxy(upb_Array* arr, protos::Arena& arena) {
+    return StringRef<T>({arr, arena.ptr(), 0});
+  }
+
+  template <typename T>
+  static StringIterator<T> MakeStringIterator(upb_Array* arr,
+                                              protos::Arena& arena) {
+    return StringIterator<T>({arr, arena.ptr()});
+  }
+};
+
+namespace {
+
+TEST(ScalarReferenceTest, BasicOperationsWork) {
+  int i = 0;
+  ScalarRef<int> p = IteratorTestPeer::MakeScalarRefProxy(i);
+  ScalarRef<const int> cp =
+      IteratorTestPeer::MakeScalarRefProxy(std::as_const(i));
+  EXPECT_EQ(i, 0);
+  p = 17;
+  EXPECT_EQ(i, 17);
+  EXPECT_EQ(p, 17);
+  EXPECT_EQ(cp, 17);
+  i = 13;
+  EXPECT_EQ(p, 13);
+  EXPECT_EQ(cp, 13);
+
+  EXPECT_FALSE((std::is_assignable<decltype(cp), int>::value));
+
+  // Check that implicit conversion works T -> const T
+  ScalarRef<const int> cp2 = p;
+  EXPECT_EQ(cp2, 13);
+
+  EXPECT_FALSE((std::is_convertible<decltype(cp), ScalarRef<int>>::value));
+}
+
+TEST(ScalarReferenceTest, AssignmentAndSwap) {
+  int i = 3;
+  int j = 5;
+  ScalarRef<int> p = IteratorTestPeer::MakeScalarRefProxy(i);
+  ScalarRef<int> p2 = IteratorTestPeer::MakeScalarRefProxy(j);
+
+  EXPECT_EQ(p, 3);
+  EXPECT_EQ(p2, 5);
+  swap(p, p2);
+  EXPECT_EQ(p, 5);
+  EXPECT_EQ(p2, 3);
+
+  p = p2;
+  EXPECT_EQ(p, 3);
+  EXPECT_EQ(p2, 3);
+}
+
+template <typename T, typename U>
+std::array<bool, 6> RunCompares(const T& a, const U& b) {
+  // Verify some basic properties here.
+  // Equivalencies
+  EXPECT_EQ((a == b), (b == a));
+  EXPECT_EQ((a != b), (b != a));
+  EXPECT_EQ((a < b), (b > a));
+  EXPECT_EQ((a > b), (b < a));
+  EXPECT_EQ((a <= b), (b >= a));
+  EXPECT_EQ((a >= b), (b <= a));
+
+  // Opposites
+  EXPECT_NE((a == b), (a != b));
+  EXPECT_NE((a < b), (a >= b));
+  EXPECT_NE((a > b), (a <= b));
+
+  return {{
+      (a == b),
+      (a != b),
+      (a < b),
+      (a <= b),
+      (a > b),
+      (a >= b),
+  }};
+}
+
+template <typename T>
+void TestScalarIterator(T* array) {
+  ScalarIterator<T> it = IteratorTestPeer::MakeScalarIterator(array);
+  // Copy
+  auto it2 = it;
+
+  EXPECT_THAT(RunCompares(it, it2),
+              ElementsAre(true, false, false, true, false, true));
+
+  // Increment
+  EXPECT_EQ(*++it, 11);
+  EXPECT_EQ(*it2, 10);
+  EXPECT_EQ(*it++, 11);
+  EXPECT_EQ(*it2, 10);
+  EXPECT_EQ(*it, 12);
+  EXPECT_EQ(*it2, 10);
+
+  EXPECT_THAT(RunCompares(it, it2),
+              ElementsAre(false, true, false, false, true, true));
+
+  // Assign
+  it2 = it;
+  EXPECT_EQ(*it, 12);
+  EXPECT_EQ(*it2, 12);
+
+  // Decrement
+  EXPECT_EQ(*--it, 11);
+  EXPECT_EQ(*it--, 11);
+  EXPECT_EQ(*it, 10);
+
+  it += 5;
+  EXPECT_EQ(*it, 15);
+  EXPECT_EQ(it - it2, 3);
+  EXPECT_EQ(it2 - it, -3);
+  it -= 3;
+  EXPECT_EQ(*it, 12);
+  EXPECT_EQ(it[6], 18);
+  EXPECT_EQ(it[-1], 11);
+}
+
+TEST(ScalarIteratorTest, BasicOperationsWork) {
+  int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+  TestScalarIterator<const int>(array);
+  TestScalarIterator<int>(array);
+}
+
+TEST(ScalarIteratorTest, Convertibility) {
+  int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+  ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array);
+  it += 4;
+  ScalarIterator<const int> cit = it;
+  EXPECT_EQ(*it, 14);
+  EXPECT_EQ(*cit, 14);
+  it += 2;
+  EXPECT_EQ(*it, 16);
+  EXPECT_EQ(*cit, 14);
+  cit = it;
+  EXPECT_EQ(*it, 16);
+  EXPECT_EQ(*cit, 16);
+
+  EXPECT_FALSE((std::is_convertible<ScalarIterator<const int>,
+                                    ScalarIterator<int>>::value));
+  EXPECT_FALSE((std::is_assignable<ScalarIterator<int>,
+                                   ScalarIterator<const int>>::value));
+}
+
+TEST(ScalarIteratorTest, MutabilityOnlyWorksOnMutable) {
+  int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+  ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array);
+  EXPECT_EQ(array[3], 13);
+  it[3] = 113;
+  EXPECT_EQ(array[3], 113);
+  ScalarIterator<const int> cit = it;
+  EXPECT_FALSE((std::is_assignable<decltype(*cit), int>::value));
+  EXPECT_FALSE((std::is_assignable<decltype(cit[1]), int>::value));
+}
+
+TEST(ScalarIteratorTest, IteratorReferenceInteraction) {
+  int array[10] = {10, 11, 12, 13, 14, 15, 16, 17, 18, 19};
+  ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(array);
+  EXPECT_EQ(it[4], 14);
+  // op& from references goes back to iterator.
+  ScalarIterator<int> it2 = &it[4];
+  EXPECT_EQ(it + 4, it2);
+}
+
+TEST(ScalarIteratorTest, IteratorBasedAlgorithmsWork) {
+  // We use a vector here to make testing it easier.
+  std::vector<int> v(10, 0);
+  ScalarIterator<int> it = IteratorTestPeer::MakeScalarIterator(v.data());
+  EXPECT_THAT(v, ElementsAre(0, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+  std::iota(it, it + 10, 10);
+  EXPECT_THAT(v, ElementsAre(10, 11, 12, 13, 14, 15, 16, 17, 18, 19));
+  EXPECT_EQ(it + 5, std::find(it, it + 10, 15));
+  EXPECT_EQ(145, std::accumulate(it, it + 10, 0));
+  std::sort(it, it + 10, [](int a, int b) {
+    return std::tuple(a % 2, a) < std::tuple(b % 2, b);
+  });
+  EXPECT_THAT(v, ElementsAre(10, 12, 14, 16, 18, 11, 13, 15, 17, 19));
+}
+
+const char* CloneString(protos::Arena& arena, absl::string_view str) {
+  char* data = (char*)upb_Arena_Malloc(arena.ptr(), str.size());
+  memcpy(data, str.data(), str.size());
+  return data;
+}
+upb_Array* MakeStringArray(protos::Arena& arena,
+                           const std::vector<std::string>& input) {
+  upb_Array* arr = upb_Array_New(arena.ptr(), kUpb_CType_String);
+  for (absl::string_view str : input) {
+    upb_MessageValue message_value;
+    message_value.str_val =
+        upb_StringView_FromDataAndSize(CloneString(arena, str), str.size());
+    upb_Array_Append(arr, message_value, arena.ptr());
+  }
+  return arr;
+}
+
+TEST(StringReferenceTest, BasicOperationsWork) {
+  protos::Arena arena;
+  upb_Array* arr = MakeStringArray(arena, {""});
+
+  auto read = [&] {
+    upb_MessageValue message_value = upb_Array_Get(arr, 0);
+    return absl::string_view(message_value.str_val.data,
+                             message_value.str_val.size);
+  };
+
+  StringRef<absl::string_view> p =
+      IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr, arena);
+  StringRef<const absl::string_view> cp =
+      IteratorTestPeer::MakeStringRefProxy<const absl::string_view>(arr, arena);
+  EXPECT_EQ(read(), "");
+  EXPECT_EQ(p, "");
+  p = "ABC";
+  EXPECT_EQ(read(), "ABC");
+  EXPECT_EQ(p, "ABC");
+  EXPECT_EQ(cp, "ABC");
+  const_cast<char*>(read().data())[0] = 'X';
+  EXPECT_EQ(read(), "XBC");
+  EXPECT_EQ(p, "XBC");
+  EXPECT_EQ(cp, "XBC");
+
+  EXPECT_FALSE((std::is_assignable<decltype(cp), int>::value));
+
+  // Check that implicit conversion works T -> const T
+  StringRef<const absl::string_view> cp2 = p;
+  EXPECT_EQ(cp2, "XBC");
+
+  EXPECT_FALSE(
+      (std::is_convertible<decltype(cp), StringRef<absl::string_view>>::value));
+
+  EXPECT_THAT(RunCompares(p, "XBC"),
+              ElementsAre(true, false, false, true, false, true));
+  EXPECT_THAT(RunCompares(p, "YBC"),
+              ElementsAre(false, true, true, true, false, false));
+  EXPECT_THAT(RunCompares(p, "RBC"),
+              ElementsAre(false, true, false, false, true, true));
+  EXPECT_THAT(RunCompares(p, "XB"),
+              ElementsAre(false, true, false, false, true, true));
+  EXPECT_THAT(RunCompares(p, "XBCD"),
+              ElementsAre(false, true, true, true, false, false));
+}
+
+TEST(StringReferenceTest, AssignmentAndSwap) {
+  protos::Arena arena;
+  upb_Array* arr1 = MakeStringArray(arena, {"ABC"});
+  upb_Array* arr2 = MakeStringArray(arena, {"DEF"});
+
+  auto p = IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr1, arena);
+  auto p2 =
+      IteratorTestPeer::MakeStringRefProxy<absl::string_view>(arr2, arena);
+
+  EXPECT_EQ(p, "ABC");
+  EXPECT_EQ(p2, "DEF");
+  swap(p, p2);
+  EXPECT_EQ(p, "DEF");
+  EXPECT_EQ(p2, "ABC");
+
+  p = p2;
+  EXPECT_EQ(p, "ABC");
+  EXPECT_EQ(p2, "ABC");
+}
+
+template <typename T>
+void TestStringIterator(protos::Arena& arena, upb_Array* array) {
+  StringIterator<T> it = IteratorTestPeer::MakeStringIterator<T>(array, arena);
+  // Copy
+  auto it2 = it;
+
+  EXPECT_THAT(RunCompares(it, it2),
+              ElementsAre(true, false, false, true, false, true));
+
+  // Increment
+  EXPECT_EQ(*++it, "11");
+  EXPECT_EQ(*it2, "10");
+  EXPECT_EQ(*it++, "11");
+  EXPECT_EQ(*it2, "10");
+  EXPECT_EQ(*it, "12");
+  EXPECT_EQ(*it2, "10");
+
+  EXPECT_THAT(RunCompares(it, it2),
+              ElementsAre(false, true, false, false, true, true));
+
+  // Assign
+  it2 = it;
+  EXPECT_EQ(*it, "12");
+  EXPECT_EQ(*it2, "12");
+
+  // Decrement
+  EXPECT_EQ(*--it, "11");
+  EXPECT_EQ(*it--, "11");
+  EXPECT_EQ(*it, "10");
+
+  it += 5;
+  EXPECT_EQ(*it, "15");
+  EXPECT_EQ(it - it2, 3);
+  EXPECT_EQ(it2 - it, -3);
+  it -= 3;
+  EXPECT_EQ(*it, "12");
+  EXPECT_EQ(it[6], "18");
+  EXPECT_EQ(it[-1], "11");
+}
+
+TEST(StringIteratorTest, BasicOperationsWork) {
+  protos::Arena arena;
+  auto* array = MakeStringArray(
+      arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"});
+  TestStringIterator<const absl::string_view>(arena, array);
+  TestStringIterator<absl::string_view>(arena, array);
+}
+
+TEST(StringIteratorTest, Convertibility) {
+  protos::Arena arena;
+  auto* array = MakeStringArray(
+      arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"});
+  StringIterator<absl::string_view> it =
+      IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena);
+  it += 4;
+  StringIterator<const absl::string_view> cit = it;
+  EXPECT_EQ(*it, "14");
+  EXPECT_EQ(*cit, "14");
+  it += 2;
+  EXPECT_EQ(*it, "16");
+  EXPECT_EQ(*cit, "14");
+  cit = it;
+  EXPECT_EQ(*it, "16");
+  EXPECT_EQ(*cit, "16");
+
+  EXPECT_FALSE((std::is_convertible<StringIterator<const absl::string_view>,
+                                    StringIterator<absl::string_view>>::value));
+  EXPECT_FALSE(
+      (std::is_assignable<StringIterator<absl::string_view>,
+                          StringIterator<const absl::string_view>>::value));
+}
+
+TEST(StringIteratorTest, MutabilityOnlyWorksOnMutable) {
+  protos::Arena arena;
+  auto* array = MakeStringArray(
+      arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"});
+  StringIterator<absl::string_view> it =
+      IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena);
+
+  auto read = [&] {
+    upb_MessageValue message_value = upb_Array_Get(array, 3);
+    return absl::string_view(message_value.str_val.data,
+                             message_value.str_val.size);
+  };
+
+  EXPECT_EQ(read(), "13");
+  it[3] = "113";
+  EXPECT_EQ(read(), "113");
+  StringIterator<const absl::string_view> cit = it;
+  EXPECT_FALSE((std::is_assignable<decltype(*cit), absl::string_view>::value));
+  EXPECT_FALSE(
+      (std::is_assignable<decltype(cit[1]), absl::string_view>::value));
+}
+
+TEST(StringIteratorTest, IteratorReferenceInteraction) {
+  protos::Arena arena;
+  auto* array = MakeStringArray(
+      arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"});
+  StringIterator<absl::string_view> it =
+      IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena);
+  EXPECT_EQ(it[4], "14");
+  // op& from references goes back to iterator.
+  StringIterator<absl::string_view> it2 = &it[4];
+  EXPECT_EQ(it + 4, it2);
+}
+
+TEST(StringIteratorTest, IteratorBasedAlgorithmsWork) {
+  protos::Arena arena;
+  auto* array = MakeStringArray(
+      arena, {"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"});
+  StringIterator<absl::string_view> it =
+      IteratorTestPeer::MakeStringIterator<absl::string_view>(array, arena);
+
+  auto read = [&] {
+    std::vector<absl::string_view> v;
+    for (int i = 0; i < 10; ++i) {
+      upb_MessageValue message_value = upb_Array_Get(array, i);
+      v.emplace_back(message_value.str_val.data, message_value.str_val.size);
+    }
+    return v;
+  };
+
+  EXPECT_THAT(read(), ElementsAre("10", "11", "12", "13", "14",  //
+                                  "15", "16", "17", "18", "19"));
+  std::sort(it, it + 10, [](absl::string_view a, absl::string_view b) {
+    return std::tuple(a[1] % 2, a) < std::tuple(b[1] % 2, b);
+  });
+  EXPECT_THAT(read(), ElementsAre("10", "12", "14", "16", "18",  //
+                                  "11", "13", "15", "17", "19"));
+  // Now sort with the default less.
+  std::sort(it, it + 10);
+  EXPECT_THAT(read(), ElementsAre("10", "11", "12", "13", "14",  //
+                                  "15", "16", "17", "18", "19"));
+
+  // Mutable algorithm
+  std::generate(it, it + 10,
+                [i = 0]() mutable { return std::string(i++, 'x'); });
+  EXPECT_THAT(read(),
+              ElementsAre("", "x", "xx", "xxx", "xxxx", "xxxxx", "xxxxxx",
+                          "xxxxxxx", "xxxxxxxx", "xxxxxxxxx"));
+}
+
+}  // namespace
+}  // namespace internal
+}  // namespace protos