Internal change

PiperOrigin-RevId: 970815405
diff --git a/upb/message/BUILD b/upb/message/BUILD
index c3a7004..9780a08 100644
--- a/upb/message/BUILD
+++ b/upb/message/BUILD
@@ -93,6 +93,8 @@
         "//upb/mem:internal",
         "//upb/mini_table",
         "//upb/port",
+        "//upb/wire:encode_extension",
+        "//upb/wire:encoder",
         "//upb/wire:eps_copy_input_stream",
         "//upb/wire:reader",
     ],
diff --git a/upb/message/unknown_fields.c b/upb/message/unknown_fields.c
index 7d11276..159f0d5 100644
--- a/upb/message/unknown_fields.c
+++ b/upb/message/unknown_fields.c
@@ -18,12 +18,41 @@
 #include "upb/message/internal/message.h"
 #include "upb/message/internal/types.h"
 #include "upb/mini_table/extension.h"
+#include "upb/wire/encode.h"
+#include "upb/wire/encode_extension.h"
 #include "upb/wire/eps_copy_input_stream.h"
 #include "upb/wire/reader.h"
 
 // Must be last.
 #include "upb/port/def.inc"
 
+bool upb_Message_NextWireFormatUnknown(const struct upb_Message* msg,
+                                       struct upb_Arena** arena,
+                                       upb_StringView* data, uintptr_t* iter) {
+  upb_MessageUnknown unknown;
+  if (!upb_Message_NextUnknown2(msg, &unknown, iter)) {
+    return false;
+  }
+  if (unknown.type == kUpb_MessageUnknownType_StringView) {
+    *data = unknown.value.bytes;
+    return true;
+  }
+  UPB_ASSERT(unknown.type == kUpb_MessageUnknownType_NonCanonicalExtension);
+  if (!*arena) {
+    *arena = upb_Arena_New();
+    if (!*arena) {
+      return false;
+    }
+  }
+  upb_EncodeStatus status =
+      upb_EncodeExtension(unknown.value.extension, *arena, data,
+                          /*encode_options=*/0);
+  if (status != kUpb_EncodeStatus_Ok) {
+    return false;
+  }
+  return true;
+}
+
 static upb_FindUnknownRet2 upb_FindUnknownRet2_ParseError(void) {
   return (upb_FindUnknownRet2){.status = kUpb_FindUnknown_ParseError};
 }
diff --git a/upb/message/unknown_fields.h b/upb/message/unknown_fields.h
index 94c28f5..5a0b8bc 100644
--- a/upb/message/unknown_fields.h
+++ b/upb/message/unknown_fields.h
@@ -70,6 +70,24 @@
   return false;
 }
 
+// Iterates over unknown fields in wire format (upb_StringView).
+// If an unknown field is a non-canonical extension, it is automatically
+// encoded into wire format into `*arena` using default encode options (0).
+//
+// `arena` is a pointer to `upb_Arena*`. If `*arena` is NULL when a
+// non-canonical extension is encountered, an arena will be lazily created via
+// `upb_Arena_New()`. The caller is responsible for freeing `*arena` (if
+// non-NULL) using `upb_Arena_Free(*arena)` after iteration completes.
+//
+// NOTE: Automatically encoding non-canonical extensions into wire format may
+// incur a performance penalty if non-canonical extensions are present, as
+// encoding requires allocating temporary buffers in `*arena`. Use
+// `upb_Message_NextUnknown2` if you want to inspect non-canonical extensions
+// directly without encoding them.
+UPB_NODISCARD bool upb_Message_NextWireFormatUnknown(
+    const struct upb_Message* msg, struct upb_Arena** arena,
+    upb_StringView* data, uintptr_t* iter);
+
 typedef enum {
   kUpb_FindUnknown_Ok,
   kUpb_FindUnknown_NotPresent,
diff --git a/upb/message/unknown_fields_test.cc b/upb/message/unknown_fields_test.cc
index e03a32a..5c0b8b3 100644
--- a/upb/message/unknown_fields_test.cc
+++ b/upb/message/unknown_fields_test.cc
@@ -303,4 +303,49 @@
   upb_Arena_Free(arena);
 }
 
+TEST(GeneratedCode, NextWireFormatUnknown) {
+  upb_Arena* arena = upb_Arena_New();
+
+  upb_test_ModelWithExtensions* msg = upb_test_ModelWithExtensions_new(arena);
+
+  // Add a raw unknown field string view
+  const char raw_bytes[] =
+      "\x08\x96\x01";  // tag 1 (field 1, varint), value 150
+  UPB_PRIVATE(_upb_Message_AddUnknown)(UPB_UPCAST(msg), raw_bytes, 3, arena,
+                                       kUpb_AddUnknown_Copy);
+
+  // Add non-canonical extension
+  upb_test_ModelExtension2* extension2 = upb_test_ModelExtension2_new(arena);
+  upb_test_ModelExtension2_set_i(extension2, 42);
+  bool set_ext_ok = UPB_PRIVATE(_upb_Message_SetNonCanonicalExtension)(
+      UPB_UPCAST(msg), upb_test_ModelExtension2_model_ext_ext, &extension2,
+      arena);
+  EXPECT_TRUE(set_ext_ok);
+
+  uintptr_t iter = kUpb_Message_UnknownBegin;
+  upb_StringView view;
+  upb_Arena* enc_arena = nullptr;
+
+  // First unknown should be raw string view (enc_arena remains nullptr)
+  EXPECT_TRUE(upb_Message_NextWireFormatUnknown(UPB_UPCAST(msg), &enc_arena,
+                                                &view, &iter));
+  EXPECT_EQ(view.size, 3);
+  EXPECT_EQ(memcmp(view.data, raw_bytes, 3), 0);
+  EXPECT_EQ(enc_arena, nullptr);
+
+  // Second unknown should be auto-encoded non-canonical extension (enc_arena
+  // lazily created)
+  EXPECT_TRUE(upb_Message_NextWireFormatUnknown(UPB_UPCAST(msg), &enc_arena,
+                                                &view, &iter));
+  EXPECT_GT(view.size, 0);
+  EXPECT_NE(enc_arena, nullptr);
+
+  // No more unknowns
+  EXPECT_FALSE(upb_Message_NextWireFormatUnknown(UPB_UPCAST(msg), &enc_arena,
+                                                 &view, &iter));
+
+  if (enc_arena) upb_Arena_Free(enc_arena);
+  upb_Arena_Free(arena);
+}
+
 }  // namespace