diff --git a/README.md b/README.md index f310392..b3b9feb 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,38 @@ License

+> To use this package you need to install php extension `protobuf`. Installation instructions are below. + ## About Protobuf Steam Auth This package provides the ability to authorize to the Steam using Google Protobuf. ## Installation -Run this text in a console to install this package from Packagist: +1. Run this text in a console to install this package from Packagist: ``` composer require allyans3/protobuf-steam-auth ``` +2. Copy folder `protobuf-ext` from `/vendor/allyans3/protobuf-steam-auth` to your project: + +``` +cp -a /vendor/allyans3/protobuf-steam-auth/protobuf-ext . +``` + +3. Build php extension with next commands: + +``` +cd protobuf-ext +phpize +./configure +make +make install +``` + +4. Add line `extension=protobuf.so` to your php.ini file + ## Usage ```php diff --git a/protobuf-ext/config.m4 b/protobuf-ext/config.m4 new file mode 100644 index 0000000..e977962 --- /dev/null +++ b/protobuf-ext/config.m4 @@ -0,0 +1,11 @@ +dnl $Id$ +dnl config.m4 for extension protobuf + +PHP_ARG_ENABLE(protobuf, whether to enable protobuf support, +[ --enable-protobuf enable protobuf support]) + +if test "$PHP_PROTOBUF" != "no"; then + PHP_NEW_EXTENSION(protobuf, protobuf.c reader.c writer.c, $ext_shared) +fi + +PHP_C_BIGENDIAN() diff --git a/protobuf-ext/php_protobuf.h b/protobuf-ext/php_protobuf.h new file mode 100755 index 0000000..4448b6c --- /dev/null +++ b/protobuf-ext/php_protobuf.h @@ -0,0 +1,13 @@ +#ifndef PROTOBUF_PHP_PHP_PROTOBUF_H +#define PROTOBUF_PHP_PHP_PROTOBUF_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#define PHP_PROTOBUF_VERSION "0.12.3" +#define PHP_PROTOBUF_EXTNAME "protobuf" + +extern zend_module_entry protobuf_module_entry; + +#endif /* PROTOBUF_PHP_PHP_PROTOBUF_H */ diff --git a/protobuf-ext/protobuf.c b/protobuf-ext/protobuf.c new file mode 100644 index 0000000..b4efeb6 --- /dev/null +++ b/protobuf-ext/protobuf.c @@ -0,0 +1,1250 @@ +#include +#include +#include + +#include "php_protobuf.h" +#include "protobuf.h" +#include "reader.h" +#include "writer.h" + +#define PB_COMPILE_ERROR(message, ...) PB_COMPILE_ERROR_EX(getThis(), message, __VA_ARGS__) +#define PB_COMPILE_ERROR_EX(this, message, ...) \ + zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s: compile error - " #message, ZSTR_VAL(Z_OBJCE_P(this)->name), __VA_ARGS__) +#define PB_CONSTANT(name) \ + zend_declare_class_constant_long(pb_entry, #name, sizeof(#name) - 1, name TSRMLS_CC) +#define PB_PARSE_ERROR(message, ...) \ + PB_PARSE_ERROR_EX(getThis(), message, __VA_ARGS__) +#define PB_PARSE_ERROR_EX(this, message, ...) \ + zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s: parse error - " #message, ZSTR_VAL(Z_OBJCE_P(this)->name), __VA_ARGS__) +#define IS_BOOL(zval) ((Z_TYPE(zval) == IS_FALSE) || (Z_TYPE(zval) == IS_TRUE)) + +#define PB_RESET_METHOD "reset" +#define PB_DUMP_METHOD "dump" +#define PB_FIELDS_METHOD "fields" +#define PB_PARSE_FROM_STRING_METHOD "parseFromString" +#define PB_SERIALIZE_TO_STRING_METHOD "serializeToString" +#define PB_PRINT_DEBUG_STRING_METHOD "printDebugString" + +#define PB_FIELD_NAME "name" +#define PB_FIELD_PACKED "packed" +#define PB_FIELD_REQUIRED "required" +#define PB_FIELD_TYPE "type" +#define PB_VALUES_PROPERTY "values" + +#define RETURN_THIS() RETURN_ZVAL(getThis(), 1, 0); + +// when opcache is enabled initial array could be stored in +// shared memory as an immutable one, in this case it should be separated +#define ARRAY_ADD_NEXT_INDEX(array, zval_p) \ + if (Z_IMMUTABLE_P(array) || Z_REFCOUNTED_P(array)) SEPARATE_ARRAY(array); \ + add_next_index_zval((array), (zval_p)) + +#define IS_32_BIT (sizeof(zend_long) < sizeof(int64_t)) + +#define ZVAL_INT64(zval, value) \ + if ((value) > ZEND_LONG_MAX || (value) < ZEND_LONG_MIN) { \ + ZVAL_DOUBLE(zval, (double)(value)); \ + } else { \ + ZVAL_LONG(zval, (zend_long)(value)); \ + } + +#define Z_LVAL_INT64(zval, int64_out_p) \ + if (Z_TYPE_P(zval) == IS_DOUBLE) { \ + *int64_out_p = (int64_t)Z_DVAL_P(zval); \ + } else { \ + *int64_out_p = (int64_t)Z_LVAL_P(zval); \ + } + +enum +{ + PB_TYPE_DOUBLE = 1, + PB_TYPE_FIXED32, + PB_TYPE_FIXED64, + PB_TYPE_FLOAT, + PB_TYPE_INT, + PB_TYPE_SIGNED_INT, + PB_TYPE_STRING, + PB_TYPE_BOOL +}; + +zend_class_entry *pb_entry; + +static int pb_assign_value(zval *this, zval *dst, zval *src, zend_ulong field_number); +static int pb_print_field_value(zval *value, zend_long level, zend_bool only_set); +static int pb_dump_field_value(zval *value, zend_long level, zend_bool only_set); +static int pb_print_debug_field_value(zval *value, zend_long level); +static zval *pb_get_field_type(zval *this, zval *field_descriptors, zend_ulong field_number); +static zval *pb_get_field_descriptor(zval *this, zval *field_descriptors, zend_ulong field_number); +static int pb_get_field_descriptors(zval *this, zval* return_value); +static const char *pb_get_field_name(zval *this, zend_ulong field_number); + +static int pb_get_wire_type(int field_type); +static const char *pb_get_wire_type_name(int wire_type); +static zval *pb_get_value(zval *this, zval *values, zend_ulong field_number); +static zval *pb_get_values(zval *this); +static int pb_parse_field_value(zval *this, reader_t *reader, zend_ulong field_number, zend_long field_type, zval *value); +static int pb_serialize_field_value(zval *this, writer_t *writer, zend_ulong field_number, zval *type, zval *value); +static int pb_serialize_packed_field(zval *this, writer_t *writer, zend_ulong field_number, zend_long field_type, zval *values); +static int pb_is_field_packed(zval *field_descriptor); + +PHP_METHOD(ProtobufMessage, __construct) +{ + zval values; + array_init(&values); + + add_property_zval(getThis(), PB_VALUES_PROPERTY, &values); + zval_ptr_dtor(&values); +} + +PHP_METHOD(ProtobufMessage, append) +{ + zend_long field_number; + zval *array, *value, *values, val; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &field_number, &value) == FAILURE) { + RETURN_THIS(); + } + + if (Z_TYPE_P(value) == IS_NULL) + RETURN_THIS(); + + if ((values = pb_get_values(getThis())) == NULL) + RETURN_THIS(); + + if ((array = pb_get_value(getThis(), values, (zend_ulong)field_number)) == NULL) + RETURN_THIS(); + + if (pb_assign_value(getThis(), &val, value, (zend_ulong)field_number) != 0) { + RETURN_THIS(); + } + + ARRAY_ADD_NEXT_INDEX(array, &val); + RETURN_THIS(); +} + +PHP_METHOD(ProtobufMessage, clear) +{ + zend_long field_number; + zval *array, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &field_number) == FAILURE) { + return; + } + + if ((values = pb_get_values(getThis())) == NULL) + RETURN_THIS(); + + if ((array = pb_get_value(getThis(), values, (zend_ulong)field_number)) == NULL) + RETURN_THIS(); + + if (Z_TYPE_P(array) != IS_ARRAY) { + PB_COMPILE_ERROR("'%s' field internal type should be an array", pb_get_field_name(getThis(), (zend_ulong)field_number)); + + RETURN_THIS(); + } + + zend_hash_clean(Z_ARRVAL_P(array)); + RETURN_THIS(); +} + +PHP_METHOD(ProtobufMessage, printDebugString) +{ + int indent; + char indent_char; + zend_long level = 0; + const char *field_name; + zend_ulong field_number, index; + HashPosition i, j; + zval *field_descriptor, field_descriptors, *val, *value, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &level) == FAILURE || level < 0) { + return; + } + + indent = ((int)level) * 2; + indent_char = indent ? ' ': '\0'; + + if ((values = pb_get_values(getThis())) == NULL) + return; + + if (pb_get_field_descriptors(getThis(), &field_descriptors)) + return; + + int is_loop_finished = 0; + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + if (is_loop_finished) + break; + + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), NULL, &field_number, &i); + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + + if ((field_descriptor = pb_get_field_descriptor(getThis(), &field_descriptors, field_number)) == NULL) + break; + + if ((field_name = pb_get_field_name(getThis(), field_number)) == NULL) + break; + + if (Z_TYPE_P(value) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(value)) == 0) + continue; + + zval *field_type; + if ((field_type = pb_get_field_type(getThis(), field_descriptor, field_number)) == NULL) + break; + + int wire = pb_get_wire_type(Z_LVAL_P(field_type)); + + PB_FOREACH(&j, Z_ARRVAL_P(value)) { + zend_hash_get_current_key_ex(Z_ARRVAL_P(value), NULL, &index, &j); + val = zend_hash_get_current_data_ex(Z_ARRVAL_P(value), &j); + + if (Z_TYPE_P(value) == IS_NULL) // make sure it's not Z_TYPE_P(val) + continue; + + if ((wire == WIRE_TYPE_LENGTH_DELIMITED && Z_TYPE_P(val) != IS_STRING) || wire == -1) { + php_printf("%*c%s {", indent, indent_char, field_name); + if (pb_print_debug_field_value(val, level + 1) != 0) { + is_loop_finished = 1; + break; + } + php_printf("%*c}\n", indent, indent_char); + } else { + php_printf("%*c%s:", indent, indent_char, field_name); + if (pb_print_debug_field_value(val, level + 1) != 0) { + is_loop_finished = 1; + break; + } + } + } + } else if (Z_TYPE_P(value) == IS_OBJECT) { + php_printf("%*c%s {", indent, indent_char, field_name); + if (pb_print_debug_field_value(value, level + 1) != 0) + break; + php_printf("%*c}\n", indent, indent_char); + } else if (Z_TYPE_P(value) != IS_NULL) { + php_printf("%*c%s:", indent, indent_char, field_name); + if (pb_print_debug_field_value(value, level + 1) != 0) + break; + } + } + + zval_ptr_dtor(&field_descriptors); +} + +PHP_METHOD(ProtobufMessage, dump) +{ + zend_bool only_set = 1; + zend_long level = 0; + const char *field_name; + zend_ulong field_number, index; + HashPosition i, j; + zval *field_descriptor, field_descriptors, *val, *value, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|bl", &only_set, &level) == FAILURE || level < 0) { + return; + } + + if (pb_get_field_descriptors(getThis(), &field_descriptors)) + return; + + if ((values = pb_get_values(getThis())) == NULL) + goto fail0; + + if (level > 0) + php_printf("%*c%s {\n", 2 * (int) level, ' ', ZSTR_VAL(Z_OBJCE_P(getThis())->name)); + else + php_printf("%s {\n", ZSTR_VAL(Z_OBJCE_P(getThis())->name)); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + zend_hash_get_current_key_ex(Z_ARRVAL_P(values), NULL, &field_number, &i); + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + if ((field_descriptor = pb_get_field_descriptor(getThis(), &field_descriptors, field_number)) == NULL) + goto fail0; + + if ((field_name = pb_get_field_name(getThis(), field_number)) == NULL) + goto fail0; + + if (Z_TYPE_P(value) == IS_ARRAY) { + if (zend_hash_num_elements(Z_ARRVAL_P(value)) > 0 || !only_set) { + php_printf("%*c%lu: %s(%u) => \n", ((int) level + 1) * 2, ' ', (uint64_t)field_number, field_name, zend_hash_num_elements(Z_ARRVAL_P(value))); + + if (zend_hash_num_elements(Z_ARRVAL_P(value)) > 0) { + PB_FOREACH(&j, Z_ARRVAL_P(value)) { + zend_hash_get_current_key_ex(Z_ARRVAL_P(value), NULL, &index, &j); + val = zend_hash_get_current_data_ex(Z_ARRVAL_P(value), &j); + + php_printf("%*c[%lu] =>", ((int) level + 2) * 2, ' ', (uint64_t)index); + + if (pb_dump_field_value(val, level + 3, only_set) != 0) + goto fail0; + } + } else + php_printf("%*cempty\n", ((int) level + 2) * 2, ' '); + } + } else if (Z_TYPE_P(value) != IS_NULL || !only_set) { + php_printf("%*c%lu: %s =>", 2 * ((int) level + 1), ' ', (uint64_t)field_number, field_name); + + if (pb_dump_field_value(value, level + 1, only_set) != 0) + goto fail0; + } + } + + if (level > 0) + php_printf("%*c}\n", 2 * (int) level, ' '); + else + php_printf("}\n"); + +fail0: + zval_ptr_dtor(&field_descriptors); +} + +PHP_METHOD(ProtobufMessage, count) +{ + zend_long field_number; + zval *value, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &field_number) == FAILURE) { + return; + } + + if ((values = pb_get_values(getThis())) == NULL) + return; + + if ((value = pb_get_value(getThis(), values, (zend_ulong)field_number)) == NULL) + return; + + if (Z_TYPE_P(value) == IS_ARRAY) { + RETURN_LONG(zend_hash_num_elements(Z_ARRVAL_P(value))); + } else { + PB_COMPILE_ERROR("'%s' field internal type should be an array", pb_get_field_name(getThis(), (zend_ulong)field_number)); + return; + } +} + +PHP_METHOD(ProtobufMessage, get) +{ + zend_long field_number, index = -1; + zval *val, *value, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|l", &field_number, &index) == FAILURE) { + return; + } + + if ((values = pb_get_values(getThis())) == NULL) + return; + + if ((value = pb_get_value(getThis(), values, (zend_ulong)field_number)) == NULL) + return; + + if (index != -1) { + if (Z_TYPE_P(value) != IS_ARRAY) { + PB_COMPILE_ERROR("'%s' field internal type should be an array", pb_get_field_name(getThis(), (zend_ulong)field_number)); + return; + } + + val = zend_hash_index_find(Z_ARRVAL_P(value), index); + if (val == NULL) + return; + + value = val; + } + + RETURN_ZVAL(value, 1, 0); +} + +PHP_METHOD(ProtobufMessage, parseFromString) +{ + char *pack, *str, *subpack; + zend_class_entry *sub_ce; + reader_t reader, packed_reader; + uint8_t wire_type; + zend_ulong field_number; + uint64_t next_field_number; + int32_t int32_value; + int64_t int64_value; + int expected_wire_type, str_size, subpack_size, ret, field_repeated; + size_t pack_size; + zval arg, *args, *field_descriptor, *field_type, field_descriptors, name, *old_value, value, *values, zret; + int bool_value; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &pack, &pack_size) == FAILURE) + return; + + ZVAL_STRINGL(&name, PB_RESET_METHOD, sizeof(PB_RESET_METHOD) - 1); + + if (call_user_function(NULL, getThis(), &name, &zret, 0, NULL TSRMLS_CC) == FAILURE) { + zval_ptr_dtor(&name); + return; + } + + zval_ptr_dtor(&name); + zval_ptr_dtor(&zret); + + if (pb_get_field_descriptors(getThis(), &field_descriptors)) + return; + + if ((values = pb_get_values(getThis())) == NULL) + goto fail0; + + reader_init(&reader, pack, pack_size); + + while (reader_has_more(&reader)) { + if (reader_read_tag(&reader, &next_field_number, &wire_type) != 0) + break; + field_number = (zend_ulong)next_field_number; + + if ((field_descriptor = zend_hash_index_find(Z_ARRVAL(field_descriptors), field_number)) == NULL) { + switch (wire_type) + { + case WIRE_TYPE_VARINT: + ret = reader_skip_varint(&reader); + break; + + case WIRE_TYPE_64BIT: + ret = reader_skip_64bit(&reader); + break; + + case WIRE_TYPE_LENGTH_DELIMITED: + ret = reader_skip_length_delimited(&reader); + break; + + case WIRE_TYPE_32BIT: + ret = reader_skip_32bit(&reader); + break; + + default: + PB_PARSE_ERROR("unexpected wire type %ld for unexpected %lu field", wire_type, (uint64_t)field_number); + goto fail0; + } + + if (ret != 0) { + PB_PARSE_ERROR("parse unexpected %lu field of wire type %s fail", (uint64_t)field_number, pb_get_wire_type_name(wire_type)); + goto fail0; + } + + continue; + } + + if ((field_type = pb_get_field_type(getThis(), field_descriptor, field_number)) == NULL) + goto fail0; + + if ((old_value = pb_get_value(getThis(), values, field_number)) == NULL) + goto fail0; + + ZVAL_NULL(&value); + field_repeated = (Z_TYPE_P(old_value) == IS_ARRAY); + + if (Z_TYPE_P(field_type) == IS_STRING) { + if (wire_type != WIRE_TYPE_LENGTH_DELIMITED) { + PB_PARSE_ERROR("'%s' field wire type is %s but should be %s", pb_get_field_name(getThis(), field_number), pb_get_wire_type_name(wire_type), pb_get_wire_type_name(WIRE_TYPE_LENGTH_DELIMITED)); + goto fail0; + } + if ((sub_ce = zend_lookup_class_ex(Z_STR_P(field_type), NULL, 1)) == NULL) { + PB_COMPILE_ERROR("class %s for '%s' does not exist", Z_STRVAL_P(field_type), pb_get_field_name(getThis(), field_number)); + goto fail0; + } + + if ((ret = reader_read_string(&reader, &subpack, &subpack_size)) == 0) { + object_init_ex(&value, sub_ce); + + ZVAL_STRINGL(&name, ZEND_CONSTRUCTOR_FUNC_NAME, sizeof(ZEND_CONSTRUCTOR_FUNC_NAME) - 1); + + if (call_user_function(NULL, &value, &name, &zret, 0, NULL TSRMLS_CC) == FAILURE) { + zval_ptr_dtor(&name); + goto fail0; + } + + zval_ptr_dtor(&name); + zval_ptr_dtor(&zret); + + ZVAL_STRINGL(&name, PB_PARSE_FROM_STRING_METHOD, sizeof(PB_PARSE_FROM_STRING_METHOD) - 1); + ZVAL_STRINGL(&arg, subpack, subpack_size); + + args = &arg; + if (call_user_function(NULL, &value, &name, &zret, 1, args TSRMLS_CC) == FAILURE) { + zval_ptr_dtor(&name); + goto fail0; + } + + zval_ptr_dtor(&arg); + zval_ptr_dtor(&name); + + bool_value = (Z_TYPE(zret) != IS_TRUE) && (Z_TYPE(zret) != IS_FALSE); + zval_ptr_dtor(&zret); + + if (bool_value) { + goto fail0; + } + } + } else if (Z_TYPE_P(field_type) == IS_LONG) { + expected_wire_type = pb_get_wire_type(Z_LVAL_P(field_type)); + if (field_repeated && wire_type == WIRE_TYPE_LENGTH_DELIMITED && expected_wire_type != WIRE_TYPE_LENGTH_DELIMITED) { + if (reader_read_string(&reader, &str, &str_size) != 0) { + PB_PARSE_ERROR("parse '%s' field fail", pb_get_field_name(getThis(), field_number)); + goto fail0; + } + + reader_init(&packed_reader, str, str_size); + while (reader_has_more(&packed_reader)) { + if (pb_parse_field_value(getThis(), &packed_reader, field_number, Z_LVAL_P(field_type), &value) != 0) { + return; + } + + if (Z_REFCOUNTED(value)) + Z_ADDREF(value); + ARRAY_ADD_NEXT_INDEX(old_value, &value); + zval_ptr_dtor(&value); + } + + continue; + + } else { + if (expected_wire_type != wire_type) { + PB_PARSE_ERROR("'%s' field wire type is %s but should be %s", pb_get_field_name(getThis(), field_number), pb_get_wire_type_name(wire_type), pb_get_wire_type_name(expected_wire_type)); + goto fail0; + } + + if (pb_parse_field_value(getThis(), &reader, field_number, Z_LVAL_P(field_type), &value) != 0) { + goto fail0; + } + } + } else { + PB_COMPILE_ERROR("unexpected %s type of '%s' field type in field descriptor", zend_get_type_by_const(Z_TYPE_P(field_type)), pb_get_field_name(getThis(), field_number)); + return; + } + + if (field_repeated) { + if (Z_REFCOUNTED(value)) + Z_ADDREF(value); + ARRAY_ADD_NEXT_INDEX(old_value, &value); + } else { + zval_ptr_dtor(old_value); + ZVAL_COPY(old_value, &value); + } + + zval_ptr_dtor(&value); + } + + zval_ptr_dtor(&field_descriptors); + RETURN_TRUE; + +fail0: + zval_ptr_dtor(&field_descriptors); + return; +} + +PHP_METHOD(ProtobufMessage, serializeToString) +{ + writer_t writer; + char *pack; + size_t pack_size; + zend_ulong field_number; + HashPosition i, j; + zval *array, *field_descriptor, field_descriptors, *required, *type, *value, *values; + + if ((values = pb_get_values(getThis())) == NULL) + return; + + if (pb_get_field_descriptors(getThis(), &field_descriptors)) + return; + + writer_init(&writer); + + PB_FOREACH(&i, Z_ARRVAL(field_descriptors)) { + zend_hash_get_current_key_ex(Z_ARRVAL(field_descriptors), NULL, &field_number, &i); + field_descriptor = zend_hash_get_current_data_ex(Z_ARRVAL(field_descriptors), &i); + if (Z_TYPE_P(field_descriptor) != IS_ARRAY) { + zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s: '%s' field descriptor must be an array", ZSTR_VAL(Z_OBJCE_P(getThis())->name), pb_get_field_name(getThis(), field_number)); + goto fail; + } + + if ((value = zend_hash_index_find(Z_ARRVAL_P(values), field_number)) == NULL) { + PB_COMPILE_ERROR("missing '%s' field value", pb_get_field_name(getThis(), field_number)); + goto fail; + } + + if ((type = pb_get_field_type(getThis(), field_descriptor, field_number)) == NULL) + goto fail; + + if (Z_TYPE_P(value) == IS_NULL) { + if ((required = zend_hash_str_find(Z_ARRVAL_P(field_descriptor), PB_FIELD_REQUIRED, sizeof(PB_FIELD_REQUIRED) - 1)) == NULL) { + zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s: '%s' field is required and must be set", ZSTR_VAL(Z_OBJCE_P(getThis())->name), pb_get_field_name(getThis(), field_number)); + } + + if (Z_TYPE_P(required) == IS_TRUE) { + zend_throw_exception_ex(NULL, 0 TSRMLS_CC, "%s: '%s' field is required and must be set", ZSTR_VAL(Z_OBJCE_P(getThis())->name), pb_get_field_name(getThis(), field_number)); + goto fail; + } + + continue; + } + + if (Z_TYPE_P(value) == IS_ARRAY) { + array = value; + if (pb_is_field_packed(field_descriptor)) { + if (pb_serialize_packed_field(getThis(), &writer, field_number, Z_LVAL_P(type), array) != 0) + goto fail; + } else { + PB_FOREACH(&j, Z_ARRVAL_P(array)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(array), &j); + if (pb_serialize_field_value(getThis(), &writer, field_number, type, value) != 0) + goto fail; + } + } + } else if (pb_serialize_field_value(getThis(), &writer, field_number, type, value) != 0) + goto fail; + } + + pack = writer_get_pack(&writer, &pack_size); + RETVAL_STRINGL(pack, pack_size); + + zval_ptr_dtor(&field_descriptors); + writer_free_pack(&writer); + + return; + +fail: + zval_ptr_dtor(&field_descriptors); + writer_free_pack(&writer); + + return; +} + +PHP_METHOD(ProtobufMessage, set) +{ + zend_long field_number = -1; + zval *old_value, *value, *values; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &field_number, &value) == FAILURE) { + RETURN_THIS(); + } + + if ((values = pb_get_values(getThis())) == NULL) + RETURN_THIS(); + + if ((old_value = pb_get_value(getThis(), values, (zend_ulong)field_number)) == NULL) + RETURN_THIS(); + + if (Z_TYPE_P(value) == IS_NULL) { + if (Z_TYPE_P(old_value) != IS_NULL) { + zval_ptr_dtor(old_value); + ZVAL_NULL(old_value); + } + } else { + zval_ptr_dtor(old_value); + pb_assign_value(getThis(), old_value, value, (zend_ulong)field_number); + } + + RETURN_THIS(); +} + +ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_reset, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_append, 0, 0, 2) + ZEND_ARG_INFO(0, position) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_clear, 0, 0, 1) + ZEND_ARG_INFO(0, position) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_printDebugString, 0, 0, 0) + ZEND_ARG_INFO(0, level) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_dump, 0, 0, 0) + ZEND_ARG_INFO(0, onlySet) + ZEND_ARG_INFO(0, level) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_count, 0, 0, 1) + ZEND_ARG_INFO(0, position) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_get, 0, 0, 1) + ZEND_ARG_INFO(0, position) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_parseFromString, 0, 0, 1) + ZEND_ARG_INFO(0, packed) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_serializeToString, 0, 0, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_set, 0, 0, 2) + ZEND_ARG_INFO(0, position) + ZEND_ARG_INFO(0, value) +ZEND_END_ARG_INFO() + +static zend_function_entry pb_methods[] = { + PHP_ME(ProtobufMessage, __construct, arginfo_construct, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR) + PHP_ABSTRACT_ME(ProtobufMessage, reset, arginfo_reset) + PHP_ME(ProtobufMessage, append, arginfo_append, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, clear, arginfo_clear, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, dump, arginfo_dump, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, count, arginfo_count, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, get, arginfo_get, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, parseFromString, arginfo_parseFromString, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, serializeToString, arginfo_serializeToString, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, set, arginfo_set, ZEND_ACC_PUBLIC) + PHP_ME(ProtobufMessage, printDebugString, arginfo_printDebugString, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL, 0, 0} +}; + +PHP_MINIT_FUNCTION(protobuf) +{ + zend_class_entry ce; + + INIT_CLASS_ENTRY(ce, "ProtobufMessage", pb_methods); + pb_entry = zend_register_internal_class(&ce TSRMLS_CC); + + PB_CONSTANT(PB_TYPE_DOUBLE); + PB_CONSTANT(PB_TYPE_FIXED32); + PB_CONSTANT(PB_TYPE_FIXED64); + PB_CONSTANT(PB_TYPE_FLOAT); + PB_CONSTANT(PB_TYPE_INT); + PB_CONSTANT(PB_TYPE_SIGNED_INT); + PB_CONSTANT(PB_TYPE_STRING); + PB_CONSTANT(PB_TYPE_BOOL); + + return SUCCESS; +} + +zend_module_entry protobuf_module_entry = { + STANDARD_MODULE_HEADER, + PHP_PROTOBUF_EXTNAME, + NULL, + PHP_MINIT(protobuf), + NULL, + NULL, + NULL, + NULL, + PHP_PROTOBUF_VERSION, + STANDARD_MODULE_PROPERTIES +}; + +#ifdef COMPILE_DL_PROTOBUF +ZEND_GET_MODULE(protobuf) +#endif + +static int pb_assign_value(zval *this, zval *dst, zval *src, zend_ulong field_number) +{ + zval *field_descriptor, field_descriptors, tmp, *type; + TSRMLS_FETCH(); + + if (pb_get_field_descriptors(this, &field_descriptors)) + goto fail0; + + if ((field_descriptor = pb_get_field_descriptor(this, &field_descriptors, field_number)) == NULL) + goto fail1; + + if ((type = pb_get_field_type(this, field_descriptor, field_number)) == NULL) + goto fail1; + + ZVAL_DUP(&tmp, src); + + if (Z_TYPE_P(type) == IS_LONG) { + switch (Z_LVAL_P(type)) + { + case PB_TYPE_DOUBLE: + case PB_TYPE_FLOAT: + if (Z_TYPE(tmp) != IS_DOUBLE) + convert_to_explicit_type(&tmp, IS_DOUBLE); + break; + + case PB_TYPE_FIXED32: + if (Z_TYPE(tmp) != IS_LONG) + convert_to_explicit_type(&tmp, IS_LONG); + break; + + case PB_TYPE_BOOL: + if (!IS_BOOL(tmp)) { + convert_to_boolean(&tmp); + } + break; + + case PB_TYPE_INT: + case PB_TYPE_FIXED64: + case PB_TYPE_SIGNED_INT: + if ((Z_TYPE(tmp) == IS_DOUBLE) && IS_32_BIT) { + if ((double)ZEND_LONG_MAX < Z_DVAL(tmp)) { + // store big value for 32bit systems as double + break; + } + } + + convert_to_explicit_type(&tmp, IS_LONG); + break; + + case PB_TYPE_STRING: + if (Z_TYPE(tmp) != IS_STRING) + convert_to_explicit_type(&tmp, IS_STRING); + + break; + + default: + PB_COMPILE_ERROR_EX(this, "unexpected '%s' field type %d in field descriptor", pb_get_field_name(this, field_number), zend_get_type_by_const(Z_LVAL_P(type))); + goto fail2; + } + + } else if (Z_TYPE_P(type) != IS_STRING) { + PB_COMPILE_ERROR_EX(this, "unexpected %s type of '%s' field type in field descriptor", zend_get_type_by_const(Z_TYPE_P(type)), pb_get_field_name(this, field_number)); + goto fail2; + } + + ZVAL_COPY_VALUE(dst, &tmp); + + zval_ptr_dtor(&field_descriptors); + return 0; +fail2: + zval_ptr_dtor(&tmp); +fail1: + zval_ptr_dtor(&field_descriptors); +fail0: + return -1; +} + +static int pb_print_field_value(zval *value, zend_long level, zend_bool only_set) +{ + const char *string_value; + zval tmp; + TSRMLS_FETCH(); + + if (Z_TYPE_P(value) == IS_NULL) + string_value = "null"; + else if (Z_TYPE_P(value) == IS_TRUE) + string_value = "true"; + else if (Z_TYPE_P(value) == IS_FALSE) + string_value = "false"; + else { + ZVAL_DUP(&tmp, value); + convert_to_string(&tmp); + string_value = Z_STRVAL(tmp); + } + + if (Z_TYPE_P(value) == IS_STRING) + php_printf(" \"%s\"\n", string_value); + else + php_printf(" %s\n", string_value); + + zval_dtor(&tmp); + + return 0; +} + +static int pb_print_debug_field_value(zval *value, zend_long level) +{ + zval tmp, ret, arg0, args[1]; + int dump_result; + TSRMLS_FETCH(); + + ZVAL_UNDEF(&ret); + + if (Z_TYPE_P(value) == IS_OBJECT) { + php_printf("\n"); + + ZVAL_LONG(&arg0, level); + args[0] = arg0; + + ZVAL_STRINGL(&tmp, PB_PRINT_DEBUG_STRING_METHOD, sizeof(PB_PRINT_DEBUG_STRING_METHOD) - 1); + + dump_result = call_user_function(NULL, value, &tmp, &ret, 1, args TSRMLS_CC); + zval_ptr_dtor(&tmp); + + if (dump_result == FAILURE) + return -1; + else + return 0; + + zval_ptr_dtor(&ret); + } + + return pb_print_field_value(value, level, 1); +} + +static int pb_dump_field_value(zval *value, zend_long level, zend_bool only_set) +{ + const char *string_value; + zval tmp, ret, arg0, arg1, args[2]; + int dump_result; + TSRMLS_FETCH(); + + ZVAL_UNDEF(&ret); // call_user_function will segfault without this init + + if (Z_TYPE_P(value) == IS_OBJECT) { + php_printf("\n"); + + ZVAL_BOOL(&arg0, only_set); + ZVAL_LONG(&arg1, level); + + args[0] = arg0; + args[1] = arg1; + + ZVAL_STRINGL(&tmp, PB_DUMP_METHOD, sizeof(PB_DUMP_METHOD) - 1); + + dump_result = call_user_function(NULL, value, &tmp, &ret, 2, args TSRMLS_CC); + zval_ptr_dtor(&tmp); + if (dump_result == FAILURE) + return -1; + else + return 0; + } + + return pb_print_field_value(value, level, only_set); +} + +static zval *pb_get_field_descriptor(zval *this, zval *field_descriptors, zend_ulong field_number) +{ + zval *field_descriptor = NULL; + TSRMLS_FETCH(); + + field_descriptor = zend_hash_index_find(Z_ARRVAL_P(field_descriptors), field_number); + if (field_descriptor == NULL) + PB_COMPILE_ERROR_EX(this, "missing %lu field descriptor", (uint64_t)field_number); + + return field_descriptor; +} + +static zval *pb_get_field_type(zval *this, zval *field_descriptor, zend_ulong field_number) +{ + zval *field_type; + TSRMLS_FETCH(); + + field_type = zend_hash_str_find(Z_ARRVAL_P(field_descriptor), PB_FIELD_TYPE, sizeof(PB_FIELD_TYPE) - 1); + if (field_type == NULL) + PB_COMPILE_ERROR_EX(this, "missing '%s' field type property in field descriptor", pb_get_field_name(this, field_number)); + + return field_type; +} + +static int pb_get_field_descriptors(zval *this, zval* return_value) +{ + zval descriptors, method; + TSRMLS_FETCH(); + + ZVAL_STRINGL(&method, PB_FIELDS_METHOD, sizeof(PB_FIELDS_METHOD) - 1); + if (call_user_function_ex(NULL, this, &method, &descriptors, 0, NULL, 0, NULL TSRMLS_CC) == FAILURE) { + return -1; + } + *return_value = descriptors; + zval_ptr_dtor(&method); + + return 0; +} + +static const char *pb_get_field_name(zval *this, zend_ulong field_number) +{ + zval *field_descriptor, field_descriptors, *field_name; + TSRMLS_FETCH(); + + if (pb_get_field_descriptors(this, &field_descriptors)) + return NULL; + + if ((field_descriptor = pb_get_field_descriptor(this, &field_descriptors, field_number)) == NULL) + goto fail0; + + field_name = zend_hash_str_find(Z_ARRVAL_P(field_descriptor), PB_FIELD_NAME, sizeof(PB_FIELD_NAME) - 1); + if (field_name == NULL) { + PB_COMPILE_ERROR_EX(this, "missing %lu field name property in field descriptor", (uint64_t)field_number); + goto fail0; + } + + zval_ptr_dtor(&field_descriptors); + return (const char *) Z_STRVAL_P(field_name); + +fail0: + zval_ptr_dtor(&field_descriptors); + return NULL; +} + +static int pb_get_wire_type(int field_type) +{ + int ret; + + switch (field_type) + { + case PB_TYPE_DOUBLE: + case PB_TYPE_FIXED64: + ret = WIRE_TYPE_64BIT; + break; + + case PB_TYPE_FIXED32: + case PB_TYPE_FLOAT: + ret = WIRE_TYPE_32BIT; + break; + + case PB_TYPE_INT: + case PB_TYPE_SIGNED_INT: + case PB_TYPE_BOOL: + ret = WIRE_TYPE_VARINT; + break; + + case PB_TYPE_STRING: + ret = WIRE_TYPE_LENGTH_DELIMITED; + break; + + default: + ret = -1; + break; + } + + return ret; +} + +static const char *pb_get_wire_type_name(int wire_type) +{ + const char *name; + + switch (wire_type) + { + case WIRE_TYPE_VARINT: + name = "varint"; + break; + + case WIRE_TYPE_64BIT: + name = "64bit"; + break; + + case WIRE_TYPE_LENGTH_DELIMITED: + name = "length-delimited"; + break; + + case WIRE_TYPE_32BIT: + name = "32bit"; + break; + + default: + name = "unknown"; + break; + } + + return name; +} + +static zval *pb_get_value(zval *this, zval *values, zend_ulong field_number) +{ + zval *value = NULL; + TSRMLS_FETCH(); + + value = zend_hash_index_find(Z_ARRVAL_P(values), field_number); + if (value == NULL) + PB_COMPILE_ERROR_EX(this, "missing %lu field value", (uint64_t)field_number); + + return value; +} + +static zval *pb_get_values(zval *this) +{ + zval *values = NULL; + TSRMLS_FETCH(); + + values = zend_hash_str_find(Z_OBJPROP_P(this), PB_VALUES_PROPERTY, sizeof(PB_VALUES_PROPERTY) - 1); + return values; +} + +static int pb_parse_field_value(zval *this, reader_t *reader, zend_ulong field_number, zend_long field_type, zval *value) +{ + int ret, str_size; + char *str; + int32_t int32_value; + int64_t int64_value; + + switch (field_type) + { + case PB_TYPE_DOUBLE: + convert_to_double(value); + ret = reader_read_double(reader, &Z_DVAL_P(value)); + break; + + case PB_TYPE_FIXED32: + convert_to_long(value); + ret = reader_read_fixed32(reader, &int32_value); + ZVAL_LONG(value, (zend_long)int32_value); + break; + + case PB_TYPE_FIXED64: + ret = reader_read_fixed64(reader, &int64_value); + ZVAL_INT64(value, int64_value); + break; + + case PB_TYPE_FLOAT: + convert_to_double(value); + ret = reader_read_float(reader, &Z_DVAL_P(value)); + break; + + case PB_TYPE_INT: + ret = reader_read_int(reader, &int64_value); + ZVAL_INT64(value, int64_value); + break; + + case PB_TYPE_BOOL: + ret = reader_read_int(reader, &int64_value); + ZVAL_BOOL(value, int64_value); + break; + + case PB_TYPE_SIGNED_INT: + ret = reader_read_signed_int(reader, &int64_value); + ZVAL_INT64(value, int64_value); + break; + + case PB_TYPE_STRING: + if ((ret = reader_read_string(reader, &str, &str_size)) == 0) + ZVAL_STRINGL(value, str, str_size); + break; + + default: + PB_COMPILE_ERROR_EX(this, "unexpected '%s' field type %d in field descriptor", pb_get_field_name(this, field_number), field_type); + return -1; + } + + if (ret != 0) { + PB_PARSE_ERROR_EX(this, "parse '%s' field fail", pb_get_field_name(this, field_number)); + } + + return ret; +} + +static int pb_serialize_field_value(zval *this, writer_t *writer, zend_ulong field_number, zval *type, zval *value) +{ + int r; + zval ret, method; + int64_t int64_value; + TSRMLS_FETCH(); + + if (Z_TYPE_P(type) == IS_STRING) { + ZVAL_STRINGL(&method, PB_SERIALIZE_TO_STRING_METHOD, sizeof(PB_SERIALIZE_TO_STRING_METHOD) - 1); + + if (call_user_function(NULL, value, &method, &ret, 0, NULL TSRMLS_CC) == FAILURE) { + zval_ptr_dtor(&method); + return -1; + } + zval_ptr_dtor(&method); + + if (Z_TYPE(ret) != IS_STRING) { + zval_ptr_dtor(&ret); + return -1; + } + + writer_write_message(writer, (uint64_t)field_number, Z_STRVAL(ret), Z_STRLEN(ret)); + + zval_ptr_dtor(&ret); + } else if (Z_TYPE_P(type) == IS_LONG) { + switch (Z_LVAL_P(type)) + { + case PB_TYPE_DOUBLE: + r = writer_write_double(writer, (uint64_t)field_number, Z_DVAL_P(value)); + break; + + case PB_TYPE_FIXED32: + r = writer_write_fixed32(writer, (uint64_t)field_number, Z_LVAL_P(value)); + break; + + case PB_TYPE_BOOL: + if (Z_TYPE_P(value) == IS_TRUE) { + int64_value = 1; + } else { + int64_value = 0; + } + r = writer_write_int(writer, (uint64_t)field_number, int64_value); + break; + + case PB_TYPE_INT: + Z_LVAL_INT64(value, &int64_value); + r = writer_write_int(writer, (uint64_t)field_number, int64_value); + break; + + case PB_TYPE_FIXED64: + Z_LVAL_INT64(value, &int64_value); + r = writer_write_fixed64(writer, (uint64_t)field_number, int64_value); + break; + + case PB_TYPE_FLOAT: + r = writer_write_float(writer, (uint64_t)field_number, Z_DVAL_P(value)); + break; + + case PB_TYPE_SIGNED_INT: + Z_LVAL_INT64(value, &int64_value); + r = writer_write_signed_int(writer, (uint64_t)field_number, int64_value); + break; + + case PB_TYPE_STRING: + r = writer_write_string(writer, (uint64_t)field_number, Z_STRVAL_P(value), Z_STRLEN_P(value)); + break; + + default: + PB_COMPILE_ERROR_EX(this, "unexpected '%s' field type %d in field descriptor", pb_get_field_name(this, field_number), Z_LVAL_P(type)); + return -1; + } + + if (r != 0) { + return -1; + } + } else { + PB_COMPILE_ERROR_EX(this, "unexpected %s type of '%s' field type in field descriptor", zend_get_type_by_const(Z_TYPE_P(type)), pb_get_field_name(this, field_number)); + return -1; + } + + return 0; +} + +static int pb_serialize_packed_field(zval *this, writer_t *writer, zend_ulong field_number, zend_long field_type, zval *values) +{ + int ret = 0; + + switch (field_type) + { + case PB_TYPE_DOUBLE: + ret = writer_write_packed_double(writer, field_number, values); + break; + + case PB_TYPE_FIXED32: + ret = writer_write_packed_fixed32(writer, field_number, values); + break; + + case PB_TYPE_FIXED64: + ret = writer_write_packed_fixed64(writer, field_number, values); + break; + + case PB_TYPE_FLOAT: + ret = writer_write_packed_float(writer, field_number, values); + break; + + case PB_TYPE_SIGNED_INT: + ret = writer_write_packed_signed_int(writer, field_number, values); + break; + + case PB_TYPE_INT: + ret = writer_write_packed_int(writer, field_number, values); + break; + + case PB_TYPE_BOOL: + ret = writer_write_packed_bool(writer, field_number, values); + break; + + default: + PB_COMPILE_ERROR_EX(this, "unexpected '%s' type %d for packed field in field descriptor", pb_get_field_name(this, field_number), field_type); + return -1; + } + + return ret; +} + +static int pb_is_field_packed(zval *field_descriptor) +{ + zval field_name; + zval *packed; + ZVAL_STRINGL(&field_name, PB_FIELD_PACKED, sizeof(PB_FIELD_PACKED) - 1); + + packed = zend_hash_find(Z_ARRVAL_P(field_descriptor), Z_STR(field_name)); + zval_ptr_dtor(&field_name); + if (packed == NULL) { + return 0; + } + return 1; +} diff --git a/protobuf-ext/protobuf.h b/protobuf-ext/protobuf.h new file mode 100644 index 0000000..14f6f30 --- /dev/null +++ b/protobuf-ext/protobuf.h @@ -0,0 +1,31 @@ +#ifndef PROTOBUF_PHP_PROTOBUF_H +#define PROTOBUF_PHP_PROTOBUF_H + +#include + +#define PB_FOREACH(iter, hash) \ + for (zend_hash_internal_pointer_reset_ex((hash), (iter)); zend_hash_has_more_elements_ex((hash), (iter)) == SUCCESS; zend_hash_move_forward_ex((hash), (iter))) + +enum +{ + WIRE_TYPE_VARINT = 0, + WIRE_TYPE_64BIT = 1, + WIRE_TYPE_LENGTH_DELIMITED = 2, + WIRE_TYPE_32BIT = 5 +}; + +typedef union +{ + float f_val; + int32_t i_val; + uint32_t u_val; +} fixed32_t; + +typedef union +{ + double d_val; + int64_t i_val; + uint64_t u_val; +} fixed64_t; + +#endif /* PROTOBUF_PHP_PROTOBUF_H */ diff --git a/protobuf-ext/reader.c b/protobuf-ext/reader.c new file mode 100644 index 0000000..5c3dfa2 --- /dev/null +++ b/protobuf-ext/reader.c @@ -0,0 +1,231 @@ +#include + +#include "protobuf.h" +#include "reader.h" + +#define READER_LEFT(reader) (reader)->len - (reader)->pos + +static inline fixed32_t reader_read_fixed_uint32(const uint8_t *data); +static inline fixed64_t reader_read_fixed_uint64(const uint8_t *data); + +static inline int reader_read_varint(reader_t *reader, uint64_t *value); + +void reader_init(reader_t *reader, char *string, size_t len) +{ + reader->string = (const uint8_t *) string; + reader->len = len; + reader->pos = 0; +} + +int reader_has_more(reader_t *reader) +{ + return reader->len != reader->pos; +} + +int reader_read_double(reader_t *reader, double *value) +{ + fixed64_t val; + + if (READER_LEFT(reader) >= 8) + { + val = reader_read_fixed_uint64(reader->string + reader->pos); + *value = val.d_val; + reader->pos += 8; + return 0; + } + + return -1; +} + +int reader_read_fixed32(reader_t *reader, int32_t *value) +{ + fixed32_t val; + + if (READER_LEFT(reader) >= 4) + { + val = reader_read_fixed_uint32(reader->string + reader->pos); + *value = val.i_val; + reader->pos += 4; + return 0; + } + + return -1; +} + +int reader_read_fixed64(reader_t *reader, int64_t *value) +{ + fixed64_t val; + + if (READER_LEFT(reader) >= 8) + { + val = reader_read_fixed_uint64(reader->string + reader->pos); + *value = val.i_val; + reader->pos += 8; + return 0; + } + + return -1; +} + +int reader_read_float(reader_t *reader, double *value) +{ + fixed32_t val; + + if (READER_LEFT(reader) >= 4) + { + val = reader_read_fixed_uint32(reader->string + reader->pos); + *value = val.f_val; + reader->pos += 4; + return 0; + } + + return -1; +} + +int reader_read_int(reader_t *reader, int64_t *value) +{ + uint64_t val; + + if (reader_read_varint(reader, &val) != 0) + return -1; + + *value = *(int64_t *) &val; + + return 0; +} + +int reader_read_signed_int(reader_t *reader, int64_t *value) +{ + uint64_t val; + + if (reader_read_varint(reader, &val) != 0) + return -1; + + if (val & 1) + val = -((*(uint64_t *) &val) >> 1) - 1; + else + val = ((*(uint64_t *) &val) >> 1); + + *value = val; + + return 0; +} + +int reader_read_string(reader_t *reader, char **string, int *len) +{ + uint64_t l; + + if (reader_read_varint(reader, &l) != 0) + return -1; + + if (READER_LEFT(reader) < l) + return -1; + + if (string != NULL) + { + *string = (char *) (reader->string + reader->pos); + *len = l; + } + + reader->pos += l; + + return 0; +} + +int reader_read_tag(reader_t *reader, uint64_t *field_number, uint8_t *wire_type) +{ + uint64_t key; + + if (reader_read_varint(reader, &key) != 0) + return -1; + + *wire_type = key & 0x07; + *field_number = (key >> 3); + + return 0; +} + +int reader_skip_32bit(reader_t *reader) +{ + if (READER_LEFT(reader) >= 4) + { + reader->pos += 4; + return 0; + } + + return -1; +} + +int reader_skip_64bit(reader_t *reader) +{ + if (READER_LEFT(reader) >= 8) + { + reader->pos += 8; + return 0; + } + + return -1; +} + +int reader_skip_length_delimited(reader_t *reader) +{ + return reader_read_string(reader, NULL, NULL); +} + +int reader_skip_varint(reader_t *reader) +{ + return reader_read_varint(reader, NULL); +} + +static inline int reader_read_varint(reader_t *reader, uint64_t *value) +{ + int i = 0; + uint64_t byte; + uint64_t val = 0; + + while (reader_has_more(reader) && i <= 10) + { + byte = reader->string[reader->pos]; + byte &= 0x7F; + byte <<= (7 * i++); + val |= byte; + + if (reader->string[reader->pos++] <= 0x7F) + { + if (value != NULL) + *value = val; + + return 0; + } + } + + return -1; +} + +static inline fixed32_t reader_read_fixed_uint32(const uint8_t *data) +{ + fixed32_t val; +#ifndef WORDS_BIGENDIAN + memcpy (&val.u_val, data, 4); + return val; +#else + val.u_val = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); + return val; +#endif +} + +static inline fixed64_t reader_read_fixed_uint64(const uint8_t *data) +{ + fixed64_t val; +#ifndef WORDS_BIGENDIAN + memcpy (&val.u_val, data, 8); + return val; +#else + fixed32_t lo = reader_read_fixed_uint32(data); + fixed32_t hi = reader_read_fixed_uint32(data + 4); + + val.u_val = (uint64_t) lo.u_val | ((uint64_t) hi.u_val) << 32; + + return val; +#endif +} diff --git a/protobuf-ext/reader.h b/protobuf-ext/reader.h new file mode 100644 index 0000000..cf7b1b2 --- /dev/null +++ b/protobuf-ext/reader.h @@ -0,0 +1,29 @@ +#ifndef PROTOBUF_PHP_READER_H +#define PROTOBUF_PHP_READER_H + +#include +#include + +typedef struct +{ + const uint8_t *string; + size_t len; + size_t pos; +} reader_t; + +void reader_init(reader_t *reader, char *string, size_t len); +int reader_has_more(reader_t *reader); +int reader_read_double(reader_t *reader, double *value); +int reader_read_fixed32(reader_t *reader, int32_t *value); +int reader_read_fixed64(reader_t *reader, int64_t *value); +int reader_read_float(reader_t *reader, double *value); +int reader_read_int(reader_t *reader, int64_t *value); +int reader_read_signed_int(reader_t *reader, int64_t *value); +int reader_read_string(reader_t *reader, char **string, int *len); +int reader_read_tag(reader_t *reader, uint64_t *field_number, uint8_t *wire_type); +int reader_skip_32bit(reader_t *reader); +int reader_skip_64bit(reader_t *reader); +int reader_skip_length_delimited(reader_t *reader); +int reader_skip_varint(reader_t *reader); + +#endif /* PROTOBUF_PHP_READER_H */ diff --git a/protobuf-ext/writer.c b/protobuf-ext/writer.c new file mode 100644 index 0000000..e6c76aa --- /dev/null +++ b/protobuf-ext/writer.c @@ -0,0 +1,481 @@ +#include + +#include + +#include "protobuf.h" +#include "writer.h" + +#define WRITER_DATA_SIZE_INCREMENT 1024 +#define WRITER_VARINT_SPACE 10 +#define WRITER_32BIT_SPACE 4 +#define WRITER_64BIT_SPACE 8 + +static inline int writer_ensure_space(writer_t *writer, size_t len); +static inline void writer_write_varint(writer_t *writer, int64_t value); +static inline void write_fixed32(writer_t *writer, fixed32_t value); +static inline void write_fixed64(writer_t *writer, fixed64_t value); +static inline void zigzag_encode(int64_t *value); + +void writer_free_pack(writer_t *writer) +{ + if (writer->data != NULL) { + efree(writer->data); + writer->data = NULL; + + writer->size = 0; + writer->pos = 0; + } +} + +void writer_init(writer_t *writer) +{ + writer_init_ex(writer, WRITER_DATA_SIZE_INCREMENT); +} + +void writer_init_ex(writer_t *writer, size_t size) +{ + if ((writer->data = (uint8_t *) emalloc(size)) != NULL) + writer->size = size; + else + writer->size = 0; + + writer->pos = 0; +} + +char *writer_get_pack(writer_t *writer, size_t *size) +{ + *size = writer->pos; + return (char *) writer->data; +} + +int writer_write_double(writer_t *writer, uint64_t field_number, double value) +{ + int64_t key; + fixed64_t val; + + if (writer_ensure_space(writer, WRITER_VARINT_SPACE + WRITER_64BIT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_64BIT); + + writer_write_varint(writer, key); + + val.d_val = value; + write_fixed64(writer, val); + + return 0; +} + +int writer_write_fixed32(writer_t *writer, uint64_t field_number, int32_t value) +{ + int64_t key; + fixed32_t val; + + if (writer_ensure_space(writer, WRITER_VARINT_SPACE + WRITER_32BIT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_32BIT); + + writer_write_varint(writer, key); + + val.i_val = value; + write_fixed32(writer, val); + + return 0; +} + +int writer_write_fixed64(writer_t *writer, uint64_t field_number, int64_t value) +{ + int64_t key; + fixed64_t val; + + if (writer_ensure_space(writer, WRITER_VARINT_SPACE + WRITER_64BIT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_64BIT); + + writer_write_varint(writer, key); + + val.i_val = value; + write_fixed64(writer, val); + + return 0; +} + +int writer_write_float(writer_t *writer, uint64_t field_number, double value) +{ + int64_t key; + fixed32_t val; + + if (writer_ensure_space(writer, WRITER_VARINT_SPACE + WRITER_32BIT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_32BIT); + + writer_write_varint(writer, key); + + val.f_val = value; + write_fixed32(writer, val); + + return 0; +} + +int writer_write_int(writer_t *writer, uint64_t field_number, int64_t value) +{ + int64_t key; + + if (writer_ensure_space(writer, 2 * WRITER_VARINT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_VARINT); + + writer_write_varint(writer, key); + writer_write_varint(writer, value); + + return 0; +} + +int writer_write_signed_int(writer_t *writer, uint64_t field_number, int64_t value) +{ + int64_t key; + + if (writer_ensure_space(writer, 2 * WRITER_VARINT_SPACE) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_VARINT); + + writer_write_varint(writer, key); + + zigzag_encode(&value); + + writer_write_varint(writer, value); + + return 0; +} + +int writer_write_string(writer_t *writer, uint64_t field_number, const char *str, size_t len) +{ + int64_t key; + + if (writer_ensure_space(writer, 2 * WRITER_VARINT_SPACE + len) != 0) + return -1; + + key = (field_number << 3 | WIRE_TYPE_LENGTH_DELIMITED); + + writer_write_varint(writer, key); + writer_write_varint(writer, len); + + if (len > 0) { + memcpy(writer->data + writer->pos, str, len); + writer->pos += len; + } + + return 0; +} + +int writer_write_packed_double(writer_t *writer, uint64_t field_number, zval *values) +{ + fixed64_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_64BIT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val.d_val = Z_DVAL_P(value); + write_fixed64(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_fixed32(writer_t *writer, uint64_t field_number, zval *values) +{ + fixed32_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_32BIT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val.i_val = (int32_t)Z_LVAL_P(value); + write_fixed32(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_fixed64(writer_t *writer, uint64_t field_number, zval *values) +{ + fixed64_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_64BIT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val.i_val = Z_LVAL_P(value); + write_fixed64(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_float(writer_t *writer, uint64_t field_number, zval *values) +{ + fixed32_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_32BIT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val.f_val = Z_DVAL_P(value); + write_fixed32(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_int(writer_t *writer, uint64_t field_number, zval *values) +{ + int64_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_VARINT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val = Z_LVAL_P(value); + writer_write_varint(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_bool(writer_t *writer, uint64_t field_number, zval *values) +{ + int64_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_VARINT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + if (Z_TYPE_P(value) == IS_TRUE) { + val = 1; + } else { + val = 0; + } + writer_write_varint(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +int writer_write_packed_signed_int(writer_t *writer, uint64_t field_number, zval *values) +{ + int64_t val; + HashPosition i; + zval *value; + writer_t packed_writer; + int ret, num; + size_t pack_size; + char *pack; + + num = zend_hash_num_elements(Z_ARRVAL_P(values)); + if (num == 0) { + return 0; + } + + writer_init_ex(&packed_writer, WRITER_VARINT_SPACE * num); + + PB_FOREACH(&i, Z_ARRVAL_P(values)) { + value = zend_hash_get_current_data_ex(Z_ARRVAL_P(values), &i); + val = Z_LVAL_P(value); + zigzag_encode(&val); + writer_write_varint(&packed_writer, val); + } + + pack = writer_get_pack(&packed_writer, &pack_size); + ret = writer_write_string(writer, field_number, pack, pack_size); + writer_free_pack(&packed_writer); + + return ret; +} + +static inline int writer_ensure_space(writer_t *writer, size_t space) +{ + uint8_t *data; + size_t left_space; + + left_space = writer->size - writer->pos; + + if (left_space < space) { + writer->size += (space - left_space); + data = (uint8_t *) erealloc(writer->data, writer->size); + + if (data == NULL) + return -1; + + writer->data = data; + } + + return 0; +} + +static inline void writer_write_varint(writer_t *writer, int64_t value) +{ + int i; + uint8_t byte; + + if (value == 0) { + writer->data[writer->pos++] = 0; + } else if (value < 0) { + for (i = 0; i < WRITER_VARINT_SPACE - 1; i++) { + writer->data[writer->pos++] = value | 0x80; + *(uint64_t *) &value >>= 7; + } + + writer->data[writer->pos++] = value; + } else { + do { + byte = value; + value >>= 7; + + if (value != 0) + byte |= 0x80; + else + byte &= 0x7F; + + writer->data[writer->pos++] = byte; + } while (value != 0); + } +} + +static inline void write_fixed32(writer_t *writer, fixed32_t value) +{ + uint8_t *out; + out = writer->data + writer->pos; +#ifndef WORDS_BIGENDIAN + memcpy(out, &value.u_val, 4); +#else + out[0] = value.u_val; + out[1] = value.u_val >> 8; + out[2] = value.u_val >> 16; + out[3] = value.u_val >> 24; +#endif + + writer->pos += WRITER_32BIT_SPACE; +} + +static inline void write_fixed64(writer_t *writer, fixed64_t value) +{ +#ifndef WORDS_BIGENDIAN + uint8_t *out; + out = writer->data + writer->pos; + memcpy(out, &value.u_val, 8); + writer->pos += WRITER_64BIT_SPACE; +#else + fixed32_t lo; + lo.u_val = value.u_val; + + fixed32_t hi; + hi.u_val = value.u_val >> 32; + + write_fixed32(writer, lo); + write_fixed32(writer, hi); +#endif +} + +static inline void zigzag_encode(int64_t *value) +{ + if (*value < 0) + *(uint64_t *) value = ((uint64_t) (-(*value)) * 2) - 1; + else + *(uint64_t *) value = 2 * (*value); +} diff --git a/protobuf-ext/writer.h b/protobuf-ext/writer.h new file mode 100644 index 0000000..9f39f9e --- /dev/null +++ b/protobuf-ext/writer.h @@ -0,0 +1,38 @@ +#ifndef PROTOBUF_PHP_WRITER_H +#define PROTOBUF_PHP_WRITER_H + +#include +#include + +typedef struct +{ + uint8_t *data; + size_t pos; + size_t size; +} writer_t; + +void writer_free_pack(writer_t *writer); +void writer_init(writer_t *writer); +void writer_init_ex(writer_t *writer, size_t size); + +char *writer_get_pack(writer_t *writer, size_t *size); + +int writer_write_double(writer_t *writer, uint64_t field_number, double value); +int writer_write_fixed32(writer_t *writer, uint64_t field_number, int32_t value); +int writer_write_fixed64(writer_t *writer, uint64_t field_number, int64_t value); +int writer_write_float(writer_t *writer, uint64_t field_number, double value); +int writer_write_int(writer_t *writer, uint64_t field_number, int64_t value); +int writer_write_signed_int(writer_t *writer, uint64_t field_number, int64_t value); +int writer_write_string(writer_t *writer, uint64_t field_number, const char *str, size_t len); + +int writer_write_packed_double(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_fixed32(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_fixed64(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_float(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_int(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_signed_int(writer_t *writer, uint64_t field_number, zval *values); +int writer_write_packed_bool(writer_t *writer, uint64_t field_number, zval *values); + +#define writer_write_message(writer, field_number, str, len) writer_write_string((writer), (field_number), (str), (len)) + +#endif /* PROTOBUF_PHP_WRITER_H */