Protocol Buffers - Google's data interchange format (grpc依赖)
https://developers.google.com/protocol-buffers/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
518 lines
15 KiB
518 lines
15 KiB
// Protocol Buffers - Google's data interchange format |
|
// Copyright 2008 Google Inc. 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 "convert.h" |
|
|
|
#include <php.h> |
|
|
|
// This is not self-contained: it must be after other Zend includes. |
|
#include <Zend/zend_exceptions.h> |
|
|
|
#include "array.h" |
|
#include "map.h" |
|
#include "message.h" |
|
#include "php-upb.h" |
|
#include "protobuf.h" |
|
|
|
// ----------------------------------------------------------------------------- |
|
// GPBUtil |
|
// ----------------------------------------------------------------------------- |
|
|
|
static zend_class_entry* GPBUtil_class_entry; |
|
|
|
// The implementation of type checking for primitive fields is empty. This is |
|
// because type checking is done when direct assigning message fields (e.g., |
|
// foo->a = 1). Functions defined here are place holders in generated code for |
|
// pure PHP implementation (c extension and pure PHP share the same generated |
|
// code). |
|
|
|
PHP_METHOD(Util, checkInt32) {} |
|
PHP_METHOD(Util, checkUint32) {} |
|
PHP_METHOD(Util, checkInt64) {} |
|
PHP_METHOD(Util, checkUint64) {} |
|
PHP_METHOD(Util, checkEnum) {} |
|
PHP_METHOD(Util, checkFloat) {} |
|
PHP_METHOD(Util, checkDouble) {} |
|
PHP_METHOD(Util, checkBool) {} |
|
PHP_METHOD(Util, checkString) {} |
|
PHP_METHOD(Util, checkBytes) {} |
|
PHP_METHOD(Util, checkMessage) {} |
|
|
|
// The result of checkMapField() is assigned, so we need to return the first |
|
// param: |
|
// $arr = GPBUtil::checkMapField($var, |
|
// \Google\Protobuf\Internal\GPBType::INT64, |
|
// \Google\Protobuf\Internal\GPBType::INT32); |
|
PHP_METHOD(Util, checkMapField) { |
|
zval *val, *key_type, *val_type, *klass; |
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zzz|z", &val, &key_type, |
|
&val_type, &klass) == FAILURE) { |
|
return; |
|
} |
|
RETURN_COPY(val); |
|
} |
|
|
|
// The result of checkRepeatedField() is assigned, so we need to return the |
|
// first param: |
|
// $arr = GPBUtil::checkRepeatedField( |
|
// $var, \Google\Protobuf\Internal\GPBType::STRING); |
|
PHP_METHOD(Util, checkRepeatedField) { |
|
zval *val, *type, *klass; |
|
if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz|z", &val, &type, &klass) == |
|
FAILURE) { |
|
return; |
|
} |
|
RETURN_COPY(val); |
|
} |
|
|
|
// clang-format off |
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_checkPrimitive, 0, 0, 1) |
|
ZEND_ARG_INFO(0, value) |
|
ZEND_END_ARG_INFO() |
|
|
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_checkString, 0, 0, 1) |
|
ZEND_ARG_INFO(0, value) |
|
ZEND_ARG_INFO(0, check_utf8) |
|
ZEND_END_ARG_INFO() |
|
|
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_checkMessage, 0, 0, 2) |
|
ZEND_ARG_INFO(0, value) |
|
ZEND_ARG_INFO(0, class) |
|
ZEND_END_ARG_INFO() |
|
|
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_checkMapField, 0, 0, 3) |
|
ZEND_ARG_INFO(0, value) |
|
ZEND_ARG_INFO(0, key_type) |
|
ZEND_ARG_INFO(0, value_type) |
|
ZEND_ARG_INFO(0, value_class) |
|
ZEND_END_ARG_INFO() |
|
|
|
ZEND_BEGIN_ARG_INFO_EX(arginfo_checkRepeatedField, 0, 0, 2) |
|
ZEND_ARG_INFO(0, value) |
|
ZEND_ARG_INFO(0, type) |
|
ZEND_ARG_INFO(0, class) |
|
ZEND_END_ARG_INFO() |
|
|
|
static zend_function_entry util_methods[] = { |
|
PHP_ME(Util, checkInt32, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkUint32, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkInt64, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkUint64, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkEnum, arginfo_checkMessage, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkFloat, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkDouble, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkBool, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkString, arginfo_checkString, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkBytes, arginfo_checkPrimitive, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkMessage, arginfo_checkMessage, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkMapField, arginfo_checkMapField, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
PHP_ME(Util, checkRepeatedField, arginfo_checkRepeatedField, |
|
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) |
|
ZEND_FE_END |
|
}; |
|
// clang-format on |
|
|
|
// ----------------------------------------------------------------------------- |
|
// Conversion functions used from C |
|
// ----------------------------------------------------------------------------- |
|
|
|
upb_CType pbphp_dtype_to_type(upb_FieldType type) { |
|
switch (type) { |
|
#define CASE(descriptor_type, type) \ |
|
case kUpb_FieldType_##descriptor_type: \ |
|
return kUpb_CType_##type; |
|
|
|
CASE(Float, Float); |
|
CASE(Double, Double); |
|
CASE(Bool, Bool); |
|
CASE(String, String); |
|
CASE(Bytes, Bytes); |
|
CASE(Message, Message); |
|
CASE(Group, Message); |
|
CASE(Enum, Enum); |
|
CASE(Int32, Int32); |
|
CASE(Int64, Int64); |
|
CASE(UInt32, Int32); |
|
CASE(UInt64, UInt64); |
|
CASE(SInt32, Int32); |
|
CASE(SInt64, Int64); |
|
CASE(Fixed32, UInt32); |
|
CASE(Fixed64, UInt64); |
|
CASE(SFixed32, Int32); |
|
CASE(SFixed64, Int64); |
|
|
|
#undef CASE |
|
} |
|
|
|
zend_error(E_ERROR, "Unknown field type."); |
|
return 0; |
|
} |
|
|
|
static bool buftouint64(const char* ptr, const char* end, uint64_t* val) { |
|
uint64_t u64 = 0; |
|
while (ptr < end) { |
|
unsigned ch = (unsigned)(*ptr - '0'); |
|
if (ch >= 10) break; |
|
if (u64 > UINT64_MAX / 10 || u64 * 10 > UINT64_MAX - ch) { |
|
return false; |
|
} |
|
u64 *= 10; |
|
u64 += ch; |
|
ptr++; |
|
} |
|
|
|
if (ptr != end) { |
|
// In PHP tradition, we allow truncation: "1.1" -> 1. |
|
// But we don't allow 'e', eg. '1.1e2' or any other non-numeric chars. |
|
if (*ptr++ != '.') return false; |
|
|
|
for (; ptr < end; ptr++) { |
|
if (*ptr < '0' || *ptr > '9') { |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
*val = u64; |
|
return true; |
|
} |
|
|
|
static bool buftoint64(const char* ptr, const char* end, int64_t* val) { |
|
bool neg = false; |
|
uint64_t u64; |
|
|
|
if (ptr != end && *ptr == '-') { |
|
ptr++; |
|
neg = true; |
|
} |
|
|
|
if (!buftouint64(ptr, end, &u64) || u64 > (uint64_t)INT64_MAX + neg) { |
|
return false; |
|
} |
|
|
|
*val = neg ? -u64 : u64; |
|
return true; |
|
} |
|
|
|
static void throw_conversion_exception(const char* to, const zval* zv) { |
|
zval tmp; |
|
ZVAL_COPY(&tmp, zv); |
|
convert_to_string(&tmp); |
|
|
|
zend_throw_exception_ex(NULL, 0, "Cannot convert '%s' to %s", |
|
Z_STRVAL_P(&tmp), to); |
|
|
|
zval_ptr_dtor(&tmp); |
|
} |
|
|
|
bool Convert_PhpToInt64(const zval* php_val, int64_t* i64) { |
|
switch (Z_TYPE_P(php_val)) { |
|
case IS_LONG: |
|
*i64 = Z_LVAL_P(php_val); |
|
return true; |
|
case IS_DOUBLE: { |
|
double dbl = Z_DVAL_P(php_val); |
|
if (dbl > 9223372036854774784.0 || dbl < -9223372036854775808.0) { |
|
zend_throw_exception_ex(NULL, 0, "Out of range"); |
|
return false; |
|
} |
|
*i64 = dbl; /* must be guarded, overflow here is UB */ |
|
return true; |
|
} |
|
case IS_STRING: { |
|
const char* buf = Z_STRVAL_P(php_val); |
|
// PHP would accept scientific notation here, but we're going to be a |
|
// little more discerning and only accept pure integers. |
|
bool ok = buftoint64(buf, buf + Z_STRLEN_P(php_val), i64); |
|
if (!ok) { |
|
throw_conversion_exception("integer", php_val); |
|
} |
|
return ok; |
|
} |
|
default: |
|
throw_conversion_exception("integer", php_val); |
|
return false; |
|
} |
|
} |
|
|
|
static bool to_double(zval* php_val, double* dbl) { |
|
switch (Z_TYPE_P(php_val)) { |
|
case IS_LONG: |
|
*dbl = Z_LVAL_P(php_val); |
|
return true; |
|
case IS_DOUBLE: |
|
*dbl = Z_DVAL_P(php_val); |
|
return true; |
|
case IS_STRING: { |
|
zend_long lval; |
|
switch (is_numeric_string(Z_STRVAL_P(php_val), Z_STRLEN_P(php_val), &lval, |
|
dbl, false)) { |
|
case IS_LONG: |
|
*dbl = lval; |
|
return true; |
|
case IS_DOUBLE: |
|
return true; |
|
default: |
|
goto fail; |
|
} |
|
} |
|
default: |
|
fail: |
|
throw_conversion_exception("double", php_val); |
|
return false; |
|
} |
|
} |
|
|
|
static bool to_bool(zval* from, bool* to) { |
|
switch (Z_TYPE_P(from)) { |
|
case IS_TRUE: |
|
*to = true; |
|
return true; |
|
case IS_FALSE: |
|
*to = false; |
|
return true; |
|
case IS_LONG: |
|
*to = (Z_LVAL_P(from) != 0); |
|
return true; |
|
case IS_DOUBLE: |
|
*to = (Z_LVAL_P(from) != 0); |
|
return true; |
|
case IS_STRING: |
|
if (Z_STRLEN_P(from) == 0 || |
|
(Z_STRLEN_P(from) == 1 && Z_STRVAL_P(from)[0] == '0')) { |
|
*to = false; |
|
} else { |
|
*to = true; |
|
} |
|
return true; |
|
default: |
|
throw_conversion_exception("bool", from); |
|
return false; |
|
} |
|
} |
|
|
|
static bool to_string(zval* from) { |
|
if (Z_ISREF_P(from)) { |
|
ZVAL_DEREF(from); |
|
} |
|
|
|
switch (Z_TYPE_P(from)) { |
|
case IS_STRING: |
|
return true; |
|
case IS_TRUE: |
|
case IS_FALSE: |
|
case IS_LONG: |
|
case IS_DOUBLE: { |
|
zval tmp; |
|
zend_make_printable_zval(from, &tmp); |
|
ZVAL_COPY_VALUE(from, &tmp); |
|
return true; |
|
} |
|
default: |
|
throw_conversion_exception("string", from); |
|
return false; |
|
} |
|
} |
|
|
|
bool Convert_PhpToUpb(zval* php_val, upb_MessageValue* upb_val, TypeInfo type, |
|
upb_Arena* arena) { |
|
int64_t i64; |
|
|
|
if (Z_ISREF_P(php_val)) { |
|
ZVAL_DEREF(php_val); |
|
} |
|
|
|
switch (type.type) { |
|
case kUpb_CType_Int64: |
|
return Convert_PhpToInt64(php_val, &upb_val->int64_val); |
|
case kUpb_CType_Int32: |
|
case kUpb_CType_Enum: |
|
if (!Convert_PhpToInt64(php_val, &i64)) { |
|
return false; |
|
} |
|
upb_val->int32_val = i64; |
|
return true; |
|
case kUpb_CType_UInt64: |
|
if (!Convert_PhpToInt64(php_val, &i64)) { |
|
return false; |
|
} |
|
upb_val->uint64_val = i64; |
|
return true; |
|
case kUpb_CType_UInt32: |
|
if (!Convert_PhpToInt64(php_val, &i64)) { |
|
return false; |
|
} |
|
upb_val->uint32_val = i64; |
|
return true; |
|
case kUpb_CType_Double: |
|
return to_double(php_val, &upb_val->double_val); |
|
case kUpb_CType_Float: |
|
if (!to_double(php_val, &upb_val->double_val)) return false; |
|
upb_val->float_val = upb_val->double_val; |
|
return true; |
|
case kUpb_CType_Bool: |
|
return to_bool(php_val, &upb_val->bool_val); |
|
case kUpb_CType_String: |
|
case kUpb_CType_Bytes: { |
|
if (!to_string(php_val)) return false; |
|
|
|
char* ptr = Z_STRVAL_P(php_val); |
|
size_t size = Z_STRLEN_P(php_val); |
|
|
|
if (type.type == kUpb_CType_String && !utf8_range_IsValid(ptr, size)) { |
|
zend_throw_exception_ex(NULL, 0, "Invalid UTF-8 in string data"); |
|
return false; |
|
} |
|
|
|
// If arena is NULL we reference the input zval. |
|
// The resulting upb_StringView will only be value while the zval is |
|
// alive. |
|
if (arena) { |
|
char* copy = upb_Arena_Malloc(arena, size); |
|
memcpy(copy, ptr, size); |
|
ptr = copy; |
|
} |
|
|
|
upb_val->str_val = upb_StringView_FromDataAndSize(ptr, size); |
|
return true; |
|
} |
|
case kUpb_CType_Message: |
|
PBPHP_ASSERT(type.desc); |
|
return Message_GetUpbMessage(php_val, type.desc, arena, |
|
(upb_Message**)&upb_val->msg_val); |
|
} |
|
|
|
return false; |
|
} |
|
|
|
void Convert_UpbToPhp(upb_MessageValue upb_val, zval* php_val, TypeInfo type, |
|
zval* arena) { |
|
switch (type.type) { |
|
case kUpb_CType_Int64: |
|
#if SIZEOF_ZEND_LONG == 8 |
|
ZVAL_LONG(php_val, upb_val.int64_val); |
|
#else |
|
{ |
|
char buf[20]; |
|
int size = sprintf(buf, "%lld", upb_val.int64_val); |
|
ZVAL_NEW_STR(php_val, zend_string_init(buf, size, 0)); |
|
} |
|
#endif |
|
break; |
|
case kUpb_CType_UInt64: |
|
#if SIZEOF_ZEND_LONG == 8 |
|
ZVAL_LONG(php_val, upb_val.uint64_val); |
|
#else |
|
{ |
|
char buf[20]; |
|
int size = sprintf(buf, "%lld", (int64_t)upb_val.uint64_val); |
|
ZVAL_NEW_STR(php_val, zend_string_init(buf, size, 0)); |
|
} |
|
#endif |
|
break; |
|
case kUpb_CType_Int32: |
|
case kUpb_CType_Enum: |
|
ZVAL_LONG(php_val, upb_val.int32_val); |
|
break; |
|
case kUpb_CType_UInt32: { |
|
// Sign-extend for consistency between 32/64-bit builds. |
|
zend_long val = (int32_t)upb_val.uint32_val; |
|
ZVAL_LONG(php_val, val); |
|
break; |
|
} |
|
case kUpb_CType_Double: |
|
ZVAL_DOUBLE(php_val, upb_val.double_val); |
|
break; |
|
case kUpb_CType_Float: |
|
ZVAL_DOUBLE(php_val, upb_val.float_val); |
|
break; |
|
case kUpb_CType_Bool: |
|
ZVAL_BOOL(php_val, upb_val.bool_val); |
|
break; |
|
case kUpb_CType_String: |
|
case kUpb_CType_Bytes: { |
|
upb_StringView str = upb_val.str_val; |
|
ZVAL_NEW_STR(php_val, zend_string_init(str.data, str.size, 0)); |
|
break; |
|
} |
|
case kUpb_CType_Message: |
|
PBPHP_ASSERT(type.desc); |
|
Message_GetPhpWrapper(php_val, type.desc, (upb_Message*)upb_val.msg_val, |
|
arena); |
|
break; |
|
} |
|
} |
|
|
|
// Check if the field is a well known wrapper type |
|
static bool IsWrapper(const upb_MessageDef* m) { |
|
if (!m) return false; |
|
switch (upb_MessageDef_WellKnownType(m)) { |
|
case kUpb_WellKnown_DoubleValue: |
|
case kUpb_WellKnown_FloatValue: |
|
case kUpb_WellKnown_Int64Value: |
|
case kUpb_WellKnown_UInt64Value: |
|
case kUpb_WellKnown_Int32Value: |
|
case kUpb_WellKnown_UInt32Value: |
|
case kUpb_WellKnown_StringValue: |
|
case kUpb_WellKnown_BytesValue: |
|
case kUpb_WellKnown_BoolValue: |
|
return true; |
|
default: |
|
return false; |
|
} |
|
} |
|
|
|
bool Convert_PhpToUpbAutoWrap(zval* val, upb_MessageValue* upb_val, |
|
TypeInfo type, upb_Arena* arena) { |
|
const upb_MessageDef* subm = type.desc ? type.desc->msgdef : NULL; |
|
if (subm && IsWrapper(subm) && Z_TYPE_P(val) != IS_OBJECT) { |
|
// Assigning a scalar to a wrapper-typed value. We will automatically wrap |
|
// the value, so the user doesn't need to create a FooWrapper(['value': X]) |
|
// message manually. |
|
const upb_MiniTable* t = upb_MessageDef_MiniTable(subm); |
|
upb_Message* wrapper = upb_Message_New(t, arena); |
|
const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(subm, 1); |
|
upb_MessageValue msgval; |
|
if (!Convert_PhpToUpb(val, &msgval, TypeInfo_Get(val_f), arena)) |
|
return false; |
|
upb_Message_SetFieldByDef(wrapper, val_f, msgval, arena); |
|
upb_val->msg_val = wrapper; |
|
return true; |
|
} else { |
|
// Convert_PhpToUpb doesn't auto-construct messages. This means that we only |
|
// allow: |
|
// ['foo_submsg': new Foo(['a' => 1])] |
|
// not: |
|
// ['foo_submsg': ['a' => 1]] |
|
return Convert_PhpToUpb(val, upb_val, type, arena); |
|
} |
|
} |
|
|
|
void Convert_ModuleInit(void) { |
|
const char* prefix_name = "TYPE_URL_PREFIX"; |
|
zend_class_entry class_type; |
|
|
|
INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\GPBUtil", |
|
util_methods); |
|
GPBUtil_class_entry = zend_register_internal_class(&class_type); |
|
|
|
zend_declare_class_constant_string(GPBUtil_class_entry, prefix_name, |
|
strlen(prefix_name), |
|
"type.googleapis.com/"); |
|
}
|
|
|