upb: implement upb_Message_FindExtensionByNumber()

PiperOrigin-RevId: 590420044
diff --git a/upb/message/BUILD b/upb/message/BUILD
index 92a86bb..5ace2f5 100644
--- a/upb/message/BUILD
+++ b/upb/message/BUILD
@@ -140,8 +140,12 @@
 
 cc_library(
     name = "message",
+    srcs = [
+        "compat.c",
+    ],
     hdrs = [
         "array.h",
+        "compat.h",
         "map.h",
         "message.h",
     ],
diff --git a/upb/message/compat.c b/upb/message/compat.c
new file mode 100644
index 0000000..26c06db
--- /dev/null
+++ b/upb/message/compat.c
@@ -0,0 +1,30 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 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
+
+#include "upb/message/compat.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "upb/message/internal/extension.h"
+#include "upb/message/message.h"
+#include "upb/mini_table/extension.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+const upb_Message_Extension* upb_Message_FindExtensionByNumber(
+    const upb_Message* msg, uint32_t field_number) {
+  size_t count = 0;
+  const upb_Message_Extension* ext = _upb_Message_Getexts(msg, &count);
+
+  while (count--) {
+    if (upb_MiniTableExtension_Number(ext->ext) == field_number) return ext;
+    ext++;
+  }
+  return NULL;
+}
diff --git a/upb/message/compat.h b/upb/message/compat.h
new file mode 100644
index 0000000..8197c3c
--- /dev/null
+++ b/upb/message/compat.h
@@ -0,0 +1,37 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2023 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
+
+#ifndef UPB_MESSAGE_COMPAT_H_
+#define UPB_MESSAGE_COMPAT_H_
+
+#include <stdint.h>
+
+#include "upb/message/message.h"
+
+// Must be last.
+#include "upb/port/def.inc"
+
+// upb does not support mixing minitables from different sources but these
+// functions are still used by some existing users so for now we make them
+// available here. This may or may not change in the future so do not add
+// them to new code.
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Returns the extension with the given field number, or NULL on failure.
+const upb_Message_Extension* upb_Message_FindExtensionByNumber(
+    const upb_Message* msg, uint32_t field_number);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#include "upb/port/undef.inc"
+
+#endif /* UPB_MESSAGE_COMPAT_H_ */
diff --git a/upb/message/internal/extension.h b/upb/message/internal/extension.h
index 799b32a..205ecb1 100644
--- a/upb/message/internal/extension.h
+++ b/upb/message/internal/extension.h
@@ -24,14 +24,14 @@
 // This is rather wasteful for scalars (in the extreme case of bool,
 // it wastes 15 bytes). We accept this because we expect messages to be
 // the most common extension type.
-typedef struct {
+struct upb_Message_Extension {
   const upb_MiniTableExtension* ext;
   union {
     upb_StringView str;
     void* ptr;
     char scalar_data[8];
   } data;
-} upb_Message_Extension;
+};
 
 #ifdef __cplusplus
 extern "C" {
diff --git a/upb/message/message.h b/upb/message/message.h
index a475100..6491481 100644
--- a/upb/message/message.h
+++ b/upb/message/message.h
@@ -21,6 +21,8 @@
 // Must be last.
 #include "upb/port/def.inc"
 
+typedef struct upb_Message_Extension upb_Message_Extension;
+
 #ifdef __cplusplus
 extern "C" {
 #endif