]> git.proxmox.com Git - ceph.git/blob - ceph/src/jaegertracing/thrift/lib/php/src/ext/thrift_protocol/php_thrift_protocol.cpp
buildsys: switch source download to quincy
[ceph.git] / ceph / src / jaegertracing / thrift / lib / php / src / ext / thrift_protocol / php_thrift_protocol.cpp
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22
23 #include "php.h"
24 #include "zend_interfaces.h"
25 #include "zend_exceptions.h"
26 #include "php_thrift_protocol.h"
27
28 #if PHP_VERSION_ID >= 70000
29
30 #include <sys/types.h>
31 #include <arpa/inet.h>
32
33 #include <cstdint>
34 #include <stdexcept>
35 #include <algorithm>
36
37 #ifndef bswap_64
38 #define bswap_64(x) (((uint64_t)(x) << 56) | \
39 (((uint64_t)(x) << 40) & 0xff000000000000ULL) | \
40 (((uint64_t)(x) << 24) & 0xff0000000000ULL) | \
41 (((uint64_t)(x) << 8) & 0xff00000000ULL) | \
42 (((uint64_t)(x) >> 8) & 0xff000000ULL) | \
43 (((uint64_t)(x) >> 24) & 0xff0000ULL) | \
44 (((uint64_t)(x) >> 40) & 0xff00ULL) | \
45 ((uint64_t)(x) >> 56))
46 #endif
47
48 #if __BYTE_ORDER == __LITTLE_ENDIAN
49 #define htonll(x) bswap_64(x)
50 #define ntohll(x) bswap_64(x)
51 #elif __BYTE_ORDER == __BIG_ENDIAN
52 #define htonll(x) x
53 #define ntohll(x) x
54 #else
55 #error Unknown __BYTE_ORDER
56 #endif
57
58 enum TType {
59 T_STOP = 0,
60 T_VOID = 1,
61 T_BOOL = 2,
62 T_BYTE = 3,
63 T_I08 = 3,
64 T_I16 = 6,
65 T_I32 = 8,
66 T_U64 = 9,
67 T_I64 = 10,
68 T_DOUBLE = 4,
69 T_STRING = 11,
70 T_UTF7 = 11,
71 T_STRUCT = 12,
72 T_MAP = 13,
73 T_SET = 14,
74 T_LIST = 15,
75 T_UTF8 = 16,
76 T_UTF16 = 17
77 };
78
79 const int32_t VERSION_MASK = 0xffff0000;
80 const int32_t VERSION_1 = 0x80010000;
81 const int8_t T_CALL = 1;
82 const int8_t T_REPLY = 2;
83 const int8_t T_EXCEPTION = 3;
84 // tprotocolexception
85 const int INVALID_DATA = 1;
86 const int BAD_VERSION = 4;
87
88 static zend_function_entry thrift_protocol_functions[] = {
89 PHP_FE(thrift_protocol_write_binary, nullptr)
90 PHP_FE(thrift_protocol_read_binary, nullptr)
91 PHP_FE(thrift_protocol_read_binary_after_message_begin, nullptr)
92 {nullptr, nullptr, nullptr}
93 };
94
95 zend_module_entry thrift_protocol_module_entry = {
96 STANDARD_MODULE_HEADER,
97 "thrift_protocol",
98 thrift_protocol_functions,
99 nullptr,
100 nullptr,
101 nullptr,
102 nullptr,
103 nullptr,
104 "1.0",
105 STANDARD_MODULE_PROPERTIES
106 };
107
108 #ifdef COMPILE_DL_THRIFT_PROTOCOL
109 ZEND_GET_MODULE(thrift_protocol)
110 #endif
111
112 class PHPExceptionWrapper : public std::exception {
113 public:
114 PHPExceptionWrapper(zval* _ex) throw() {
115 ZVAL_COPY(&ex, _ex);
116 snprintf(_what, 40, "PHP exception zval=%p", _ex);
117 }
118
119 PHPExceptionWrapper(zend_object* _exobj) throw() {
120 ZVAL_OBJ(&ex, _exobj);
121 snprintf(_what, 40, "PHP exception zval=%p", _exobj);
122 }
123 ~PHPExceptionWrapper() throw() {
124 zval_dtor(&ex);
125 }
126
127 const char* what() const throw() {
128 return _what;
129 }
130 operator zval*() const throw() {
131 return const_cast<zval*>(&ex);
132 } // Zend API doesn't do 'const'...
133 protected:
134 zval ex;
135 char _what[40];
136 } ;
137
138 class PHPTransport {
139 protected:
140 PHPTransport(zval* _p, size_t _buffer_size) {
141 assert(Z_TYPE_P(_p) == IS_OBJECT);
142
143 ZVAL_UNDEF(&t);
144
145 buffer = reinterpret_cast<char*>(emalloc(_buffer_size));
146 buffer_ptr = buffer;
147 buffer_used = 0;
148 buffer_size = _buffer_size;
149
150 // Get the transport for the passed protocol
151 zval gettransport;
152 ZVAL_STRING(&gettransport, "getTransport");
153 call_user_function(nullptr, _p, &gettransport, &t, 0, nullptr);
154
155 zval_dtor(&gettransport);
156
157 if (EG(exception)) {
158 zend_object *ex = EG(exception);
159 EG(exception) = nullptr;
160 throw PHPExceptionWrapper(ex);
161 }
162
163 assert(Z_TYPE(t) == IS_OBJECT);
164 }
165
166 ~PHPTransport() {
167 efree(buffer);
168 zval_dtor(&t);
169 }
170
171 char* buffer;
172 char* buffer_ptr;
173 size_t buffer_used;
174 size_t buffer_size;
175
176 zval t;
177 };
178
179
180 class PHPOutputTransport : public PHPTransport {
181 public:
182 PHPOutputTransport(zval* _p, size_t _buffer_size = 8192) : PHPTransport(_p, _buffer_size) { }
183 ~PHPOutputTransport() { }
184
185 void write(const char* data, size_t len) {
186 if ((len + buffer_used) > buffer_size) {
187 internalFlush();
188 }
189 if (len > buffer_size) {
190 directWrite(data, len);
191 } else {
192 memcpy(buffer_ptr, data, len);
193 buffer_used += len;
194 buffer_ptr += len;
195 }
196 }
197
198 void writeI64(int64_t i) {
199 i = htonll(i);
200 write((const char*)&i, 8);
201 }
202
203 void writeU32(uint32_t i) {
204 i = htonl(i);
205 write((const char*)&i, 4);
206 }
207
208 void writeI32(int32_t i) {
209 i = htonl(i);
210 write((const char*)&i, 4);
211 }
212
213 void writeI16(int16_t i) {
214 i = htons(i);
215 write((const char*)&i, 2);
216 }
217
218 void writeI8(int8_t i) {
219 write((const char*)&i, 1);
220 }
221
222 void writeString(const char* str, size_t len) {
223 writeU32(len);
224 write(str, len);
225 }
226
227 void flush() {
228 internalFlush();
229 directFlush();
230 }
231
232 protected:
233 void internalFlush() {
234 if (buffer_used) {
235 directWrite(buffer, buffer_used);
236 buffer_ptr = buffer;
237 buffer_used = 0;
238 }
239 }
240 void directFlush() {
241 zval ret, flushfn;
242 ZVAL_NULL(&ret);
243 ZVAL_STRING(&flushfn, "flush");
244
245 call_user_function(EG(function_table), &(this->t), &flushfn, &ret, 0, nullptr);
246 zval_dtor(&flushfn);
247 zval_dtor(&ret);
248 if (EG(exception)) {
249 zend_object *ex = EG(exception);
250 EG(exception) = nullptr;
251 throw PHPExceptionWrapper(ex);
252 }
253 }
254 void directWrite(const char* data, size_t len) {
255 zval args[1], ret, writefn;
256
257 ZVAL_STRING(&writefn, "write");
258 ZVAL_STRINGL(&args[0], data, len);
259
260 ZVAL_NULL(&ret);
261 call_user_function(EG(function_table), &(this->t), &writefn, &ret, 1, args);
262
263 zval_dtor(&writefn);
264 zval_dtor(&ret);
265 zval_dtor(&args[0]);
266
267 if (EG(exception)) {
268 zend_object *ex = EG(exception);
269 EG(exception) = nullptr;
270 throw PHPExceptionWrapper(ex);
271 }
272 }
273 };
274
275 class PHPInputTransport : public PHPTransport {
276 public:
277 PHPInputTransport(zval* _p, size_t _buffer_size = 8192) : PHPTransport(_p, _buffer_size) {
278 }
279
280 ~PHPInputTransport() {
281 put_back();
282 }
283
284 void put_back() {
285 if (buffer_used) {
286 zval args[1], ret, putbackfn;
287 ZVAL_STRINGL(&args[0], buffer_ptr, buffer_used);
288 ZVAL_STRING(&putbackfn, "putBack");
289 ZVAL_NULL(&ret);
290
291 call_user_function(EG(function_table), &(this->t), &putbackfn, &ret, 1, args);
292
293 zval_dtor(&putbackfn);
294 zval_dtor(&ret);
295 zval_dtor(&args[0]);
296 if (EG(exception)) {
297 zend_object *ex = EG(exception);
298 EG(exception) = nullptr;
299 throw PHPExceptionWrapper(ex);
300 }
301 }
302 buffer_used = 0;
303 buffer_ptr = buffer;
304 }
305
306 void skip(size_t len) {
307 while (len) {
308 size_t chunk_size = (std::min)(len, buffer_used);
309 if (chunk_size) {
310 buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
311 buffer_used -= chunk_size;
312 len -= chunk_size;
313 }
314 if (! len) break;
315 refill();
316 }
317 }
318
319 void readBytes(void* buf, size_t len) {
320 while (len) {
321 size_t chunk_size = (std::min)(len, buffer_used);
322 if (chunk_size) {
323 memcpy(buf, buffer_ptr, chunk_size);
324 buffer_ptr = reinterpret_cast<char*>(buffer_ptr) + chunk_size;
325 buffer_used -= chunk_size;
326 buf = reinterpret_cast<char*>(buf) + chunk_size;
327 len -= chunk_size;
328 }
329 if (! len) break;
330 refill();
331 }
332 }
333
334 int8_t readI8() {
335 int8_t c;
336 readBytes(&c, 1);
337 return c;
338 }
339
340 int16_t readI16() {
341 int16_t c;
342 readBytes(&c, 2);
343 return (int16_t)ntohs(c);
344 }
345
346 uint32_t readU32() {
347 uint32_t c;
348 readBytes(&c, 4);
349 return (uint32_t)ntohl(c);
350 }
351
352 int32_t readI32() {
353 int32_t c;
354 readBytes(&c, 4);
355 return (int32_t)ntohl(c);
356 }
357
358 protected:
359 void refill() {
360 assert(buffer_used == 0);
361 zval retval;
362 zval args[1];
363 zval funcname;
364
365 ZVAL_NULL(&retval);
366 ZVAL_LONG(&args[0], buffer_size);
367
368 ZVAL_STRING(&funcname, "read");
369
370 call_user_function(EG(function_table), &(this->t), &funcname, &retval, 1, args);
371 zval_dtor(&args[0]);
372 zval_dtor(&funcname);
373
374 if (EG(exception)) {
375 zval_dtor(&retval);
376
377 zend_object *ex = EG(exception);
378 EG(exception) = nullptr;
379 throw PHPExceptionWrapper(ex);
380 }
381
382 buffer_used = Z_STRLEN(retval);
383 memcpy(buffer, Z_STRVAL(retval), buffer_used);
384
385 zval_dtor(&retval);
386
387 buffer_ptr = buffer;
388 }
389
390 };
391
392 static
393 void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec);
394 static
395 void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec);
396 static
397 void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval* value, HashTable* fieldspec);
398 static inline
399 bool ttype_is_scalar(int8_t t);
400
401 // Create a PHP object given a typename and call the ctor, optionally passing up to 2 arguments
402 static
403 void createObject(const char* obj_typename, zval* return_value, int nargs = 0, zval* arg1 = nullptr, zval* arg2 = nullptr) {
404 /* is there a better way to do that on the stack ? */
405 zend_string *obj_name = zend_string_init(obj_typename, strlen(obj_typename), 0);
406 zend_class_entry* ce = zend_fetch_class(obj_name, ZEND_FETCH_CLASS_DEFAULT);
407 zend_string_release(obj_name);
408
409 if (! ce) {
410 php_error_docref(nullptr, E_ERROR, "Class %s does not exist", obj_typename);
411 RETURN_NULL();
412 }
413
414 object_and_properties_init(return_value, ce, nullptr);
415 zend_function* constructor = zend_std_get_constructor(Z_OBJ_P(return_value));
416 zval ctor_rv;
417 zend_call_method(return_value, ce, &constructor, NULL, 0, &ctor_rv, nargs, arg1, arg2);
418 zval_dtor(&ctor_rv);
419 if (EG(exception)) {
420 zend_object *ex = EG(exception);
421 EG(exception) = nullptr;
422 throw PHPExceptionWrapper(ex);
423 }
424 }
425
426 static
427 void throw_tprotocolexception(const char* what, long errorcode) {
428 zval zwhat, zerrorcode;
429
430 ZVAL_STRING(&zwhat, what);
431 ZVAL_LONG(&zerrorcode, errorcode);
432
433 zval ex;
434 createObject("\\Thrift\\Exception\\TProtocolException", &ex, 2, &zwhat, &zerrorcode);
435
436 zval_dtor(&zwhat);
437 zval_dtor(&zerrorcode);
438
439 throw PHPExceptionWrapper(&ex);
440 }
441
442 // Sets EG(exception), call this and then RETURN_NULL();
443 static
444 void throw_zend_exception_from_std_exception(const std::exception& ex) {
445 zend_throw_exception(zend_exception_get_default(), const_cast<char*>(ex.what()), 0);
446 }
447
448 static
449 void skip_element(long thrift_typeID, PHPInputTransport& transport) {
450 switch (thrift_typeID) {
451 case T_STOP:
452 case T_VOID:
453 return;
454 case T_STRUCT:
455 while (true) {
456 int8_t ttype = transport.readI8(); // get field type
457 if (ttype == T_STOP) break;
458 transport.skip(2); // skip field number, I16
459 skip_element(ttype, transport); // skip field payload
460 }
461 return;
462 case T_BOOL:
463 case T_BYTE:
464 transport.skip(1);
465 return;
466 case T_I16:
467 transport.skip(2);
468 return;
469 case T_I32:
470 transport.skip(4);
471 return;
472 case T_U64:
473 case T_I64:
474 case T_DOUBLE:
475 transport.skip(8);
476 return;
477 //case T_UTF7: // aliases T_STRING
478 case T_UTF8:
479 case T_UTF16:
480 case T_STRING: {
481 uint32_t len = transport.readU32();
482 transport.skip(len);
483 } return;
484 case T_MAP: {
485 int8_t keytype = transport.readI8();
486 int8_t valtype = transport.readI8();
487 uint32_t size = transport.readU32();
488 for (uint32_t i = 0; i < size; ++i) {
489 skip_element(keytype, transport);
490 skip_element(valtype, transport);
491 }
492 } return;
493 case T_LIST:
494 case T_SET: {
495 int8_t valtype = transport.readI8();
496 uint32_t size = transport.readU32();
497 for (uint32_t i = 0; i < size; ++i) {
498 skip_element(valtype, transport);
499 }
500 } return;
501 };
502
503 char errbuf[128];
504 sprintf(errbuf, "Unknown thrift typeID %ld", thrift_typeID);
505 throw_tprotocolexception(errbuf, INVALID_DATA);
506 }
507
508 static inline
509 bool zval_is_bool(zval* v) {
510 return Z_TYPE_P(v) == IS_TRUE || Z_TYPE_P(v) == IS_FALSE;
511 }
512
513 static
514 void binary_deserialize(int8_t thrift_typeID, PHPInputTransport& transport, zval* return_value, HashTable* fieldspec) {
515 ZVAL_NULL(return_value);
516
517 switch (thrift_typeID) {
518 case T_STOP:
519 case T_VOID:
520 RETURN_NULL();
521 return;
522 case T_STRUCT: {
523 zval* val_ptr = zend_hash_str_find(fieldspec, "class", sizeof("class")-1);
524 if (val_ptr == nullptr) {
525 throw_tprotocolexception("no class type in spec", INVALID_DATA);
526 skip_element(T_STRUCT, transport);
527 RETURN_NULL();
528 }
529
530 char* structType = Z_STRVAL_P(val_ptr);
531 // Create an object in PHP userland based on our spec
532 createObject(structType, return_value);
533 if (Z_TYPE_P(return_value) == IS_NULL) {
534 // unable to create class entry
535 skip_element(T_STRUCT, transport);
536 RETURN_NULL();
537 }
538
539 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, false);
540 ZVAL_DEREF(spec);
541 if (EG(exception)) {
542 zend_object *ex = EG(exception);
543 EG(exception) = nullptr;
544 throw PHPExceptionWrapper(ex);
545 }
546 if (Z_TYPE_P(spec) != IS_ARRAY) {
547 char errbuf[128];
548 snprintf(errbuf, 128, "spec for %s is wrong type: %d\n", structType, Z_TYPE_P(spec));
549 throw_tprotocolexception(errbuf, INVALID_DATA);
550 RETURN_NULL();
551 }
552 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
553 return;
554 } break;
555 case T_BOOL: {
556 uint8_t c;
557 transport.readBytes(&c, 1);
558 RETURN_BOOL(c != 0);
559 }
560 //case T_I08: // same numeric value as T_BYTE
561 case T_BYTE: {
562 uint8_t c;
563 transport.readBytes(&c, 1);
564 RETURN_LONG((int8_t)c);
565 }
566 case T_I16: {
567 uint16_t c;
568 transport.readBytes(&c, 2);
569 RETURN_LONG((int16_t)ntohs(c));
570 }
571 case T_I32: {
572 uint32_t c;
573 transport.readBytes(&c, 4);
574 RETURN_LONG((int32_t)ntohl(c));
575 }
576 case T_U64:
577 case T_I64: {
578 uint64_t c;
579 transport.readBytes(&c, 8);
580 RETURN_LONG((int64_t)ntohll(c));
581 }
582 case T_DOUBLE: {
583 union {
584 uint64_t c;
585 double d;
586 } a;
587 transport.readBytes(&(a.c), 8);
588 a.c = ntohll(a.c);
589 RETURN_DOUBLE(a.d);
590 }
591 //case T_UTF7: // aliases T_STRING
592 case T_UTF8:
593 case T_UTF16:
594 case T_STRING: {
595 uint32_t size = transport.readU32();
596 if (size) {
597 char strbuf[size+1];
598 transport.readBytes(strbuf, size);
599 strbuf[size] = '\0';
600 ZVAL_STRINGL(return_value, strbuf, size);
601 } else {
602 ZVAL_EMPTY_STRING(return_value);
603 }
604 return;
605 }
606 case T_MAP: { // array of key -> value
607 uint8_t types[2];
608 transport.readBytes(types, 2);
609 uint32_t size = transport.readU32();
610 array_init(return_value);
611
612 zval *val_ptr;
613 val_ptr = zend_hash_str_find(fieldspec, "key", sizeof("key")-1);
614 HashTable* keyspec = Z_ARRVAL_P(val_ptr);
615 val_ptr = zend_hash_str_find(fieldspec, "val", sizeof("val")-1);
616 HashTable* valspec = Z_ARRVAL_P(val_ptr);
617
618 for (uint32_t s = 0; s < size; ++s) {
619 zval key, value;
620
621 binary_deserialize(types[0], transport, &key, keyspec);
622 binary_deserialize(types[1], transport, &value, valspec);
623 if (Z_TYPE(key) == IS_LONG) {
624 zend_hash_index_update(Z_ARR_P(return_value), Z_LVAL(key), &value);
625 } else {
626 if (Z_TYPE(key) != IS_STRING) convert_to_string(&key);
627 zend_symtable_update(Z_ARR_P(return_value), Z_STR(key), &value);
628 }
629 zval_dtor(&key);
630 }
631 return; // return_value already populated
632 }
633 case T_LIST: { // array with autogenerated numeric keys
634 int8_t type = transport.readI8();
635 uint32_t size = transport.readU32();
636 zval *val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
637 HashTable* elemspec = Z_ARRVAL_P(val_ptr);
638
639 array_init(return_value);
640 for (uint32_t s = 0; s < size; ++s) {
641 zval value;
642 binary_deserialize(type, transport, &value, elemspec);
643 zend_hash_next_index_insert(Z_ARR_P(return_value), &value);
644 }
645 return;
646 }
647 case T_SET: { // array of key -> TRUE
648 uint8_t type;
649 uint32_t size;
650 transport.readBytes(&type, 1);
651 transport.readBytes(&size, 4);
652 size = ntohl(size);
653 zval *val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
654 HashTable* elemspec = Z_ARRVAL_P(val_ptr);
655
656 array_init(return_value);
657
658 for (uint32_t s = 0; s < size; ++s) {
659 zval key, value;
660 ZVAL_TRUE(&value);
661
662 binary_deserialize(type, transport, &key, elemspec);
663
664 if (Z_TYPE(key) == IS_LONG) {
665 zend_hash_index_update(Z_ARR_P(return_value), Z_LVAL(key), &value);
666 } else {
667 if (Z_TYPE(key) != IS_STRING) convert_to_string(&key);
668 zend_symtable_update(Z_ARR_P(return_value), Z_STR(key), &value);
669 }
670 zval_dtor(&key);
671 }
672 return;
673 }
674 };
675
676 char errbuf[128];
677 sprintf(errbuf, "Unknown thrift typeID %d", thrift_typeID);
678 throw_tprotocolexception(errbuf, INVALID_DATA);
679 }
680
681 static
682 void binary_serialize_hashtable_key(int8_t keytype, PHPOutputTransport& transport, HashTable* ht, HashPosition& ht_pos, HashTable* spec) {
683 bool keytype_is_numeric = (!((keytype == T_STRING) || (keytype == T_UTF8) || (keytype == T_UTF16)));
684
685 zend_string* key;
686 uint key_len;
687 long index = 0;
688
689 zval z;
690
691 int res = zend_hash_get_current_key_ex(ht, &key, (zend_ulong*)&index, &ht_pos);
692 if (res == HASH_KEY_IS_STRING) {
693 ZVAL_STR_COPY(&z, key);
694 } else {
695 ZVAL_LONG(&z, index);
696 }
697 binary_serialize(keytype, transport, &z, spec);
698 zval_dtor(&z);
699 }
700
701 static
702 void binary_serialize(int8_t thrift_typeID, PHPOutputTransport& transport, zval* value, HashTable* fieldspec) {
703 if (value) {
704 ZVAL_DEREF(value);
705 }
706 // At this point the typeID (and field num, if applicable) should've already been written to the output so all we need to do is write the payload.
707 switch (thrift_typeID) {
708 case T_STOP:
709 case T_VOID:
710 return;
711 case T_STRUCT: {
712 if (Z_TYPE_P(value) != IS_OBJECT) {
713 throw_tprotocolexception("Attempt to send non-object type as a T_STRUCT", INVALID_DATA);
714 }
715 zval* spec = zend_read_static_property(Z_OBJCE_P(value), "_TSPEC", sizeof("_TSPEC")-1, true);
716 if (spec && Z_TYPE_P(spec) == IS_REFERENCE) {
717 ZVAL_DEREF(spec);
718 }
719 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
720 throw_tprotocolexception("Attempt to send non-Thrift object as a T_STRUCT", INVALID_DATA);
721 }
722 binary_serialize_spec(value, transport, Z_ARRVAL_P(spec));
723 } return;
724 case T_BOOL:
725 if (!zval_is_bool(value)) convert_to_boolean(value);
726 transport.writeI8(Z_TYPE_INFO_P(value) == IS_TRUE ? 1 : 0);
727 return;
728 case T_BYTE:
729 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
730 transport.writeI8(Z_LVAL_P(value));
731 return;
732 case T_I16:
733 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
734 transport.writeI16(Z_LVAL_P(value));
735 return;
736 case T_I32:
737 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
738 transport.writeI32(Z_LVAL_P(value));
739 return;
740 case T_I64:
741 case T_U64: {
742 int64_t l_data;
743 #if defined(_LP64) || defined(_WIN64)
744 if (Z_TYPE_P(value) != IS_LONG) convert_to_long(value);
745 l_data = Z_LVAL_P(value);
746 #else
747 if (Z_TYPE_P(value) != IS_DOUBLE) convert_to_double(value);
748 l_data = (int64_t)Z_DVAL_P(value);
749 #endif
750 transport.writeI64(l_data);
751 } return;
752 case T_DOUBLE: {
753 union {
754 int64_t c;
755 double d;
756 } a;
757 if (Z_TYPE_P(value) != IS_DOUBLE) convert_to_double(value);
758 a.d = Z_DVAL_P(value);
759 transport.writeI64(a.c);
760 } return;
761 case T_UTF8:
762 case T_UTF16:
763 case T_STRING:
764 if (Z_TYPE_P(value) != IS_STRING) convert_to_string(value);
765 transport.writeString(Z_STRVAL_P(value), Z_STRLEN_P(value));
766 return;
767 case T_MAP: {
768 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
769 if (Z_TYPE_P(value) != IS_ARRAY) {
770 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_MAP)", INVALID_DATA);
771 }
772 HashTable* ht = Z_ARRVAL_P(value);
773 zval* val_ptr;
774
775 val_ptr = zend_hash_str_find(fieldspec, "ktype", sizeof("ktype")-1);
776 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
777 uint8_t keytype = Z_LVAL_P(val_ptr);
778 transport.writeI8(keytype);
779 val_ptr = zend_hash_str_find(fieldspec, "vtype", sizeof("vtype")-1);
780 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
781 uint8_t valtype = Z_LVAL_P(val_ptr);
782 transport.writeI8(valtype);
783
784 val_ptr = zend_hash_str_find(fieldspec, "val", sizeof("val")-1);
785 HashTable* valspec = Z_ARRVAL_P(val_ptr);
786 HashTable* keyspec = Z_ARRVAL_P(zend_hash_str_find(fieldspec, "key", sizeof("key")-1));
787
788 transport.writeI32(zend_hash_num_elements(ht));
789 HashPosition key_ptr;
790 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
791 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
792 zend_hash_move_forward_ex(ht, &key_ptr)) {
793 binary_serialize_hashtable_key(keytype, transport, ht, key_ptr, keyspec);
794 binary_serialize(valtype, transport, val_ptr, valspec);
795 }
796 } return;
797 case T_LIST: {
798 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
799 if (Z_TYPE_P(value) != IS_ARRAY) {
800 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_LIST)", INVALID_DATA);
801 }
802 HashTable* ht = Z_ARRVAL_P(value);
803 zval* val_ptr;
804
805 val_ptr = zend_hash_str_find(fieldspec, "etype", sizeof("etype")-1);
806 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
807 uint8_t valtype = Z_LVAL_P(val_ptr);
808 transport.writeI8(valtype);
809
810 val_ptr = zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1);
811 HashTable* valspec = Z_ARRVAL_P(val_ptr);
812
813 transport.writeI32(zend_hash_num_elements(ht));
814 HashPosition key_ptr;
815 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
816 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
817 zend_hash_move_forward_ex(ht, &key_ptr)) {
818 binary_serialize(valtype, transport, val_ptr, valspec);
819 }
820 } return;
821 case T_SET: {
822 if (Z_TYPE_P(value) != IS_ARRAY) convert_to_array(value);
823 if (Z_TYPE_P(value) != IS_ARRAY) {
824 throw_tprotocolexception("Attempt to send an incompatible type as an array (T_SET)", INVALID_DATA);
825 }
826 HashTable* ht = Z_ARRVAL_P(value);
827 zval* val_ptr;
828
829 val_ptr = zend_hash_str_find(fieldspec, "etype", sizeof("etype")-1);
830 HashTable* spec = Z_ARRVAL_P(zend_hash_str_find(fieldspec, "elem", sizeof("elem")-1));
831 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
832 uint8_t keytype = Z_LVAL_P(val_ptr);
833 transport.writeI8(keytype);
834
835 transport.writeI32(zend_hash_num_elements(ht));
836 HashPosition key_ptr;
837 if(ttype_is_scalar(keytype)){
838 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
839 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
840 zend_hash_move_forward_ex(ht, &key_ptr)) {
841 binary_serialize_hashtable_key(keytype, transport, ht, key_ptr, spec);
842 }
843 } else {
844 for (zend_hash_internal_pointer_reset_ex(ht, &key_ptr);
845 (val_ptr = zend_hash_get_current_data_ex(ht, &key_ptr)) != nullptr;
846 zend_hash_move_forward_ex(ht, &key_ptr)) {
847 binary_serialize(keytype, transport, val_ptr, spec);
848 }
849 }
850 } return;
851 };
852
853 char errbuf[128];
854 snprintf(errbuf, 128, "Unknown thrift typeID %d", thrift_typeID);
855 throw_tprotocolexception(errbuf, INVALID_DATA);
856 }
857
858 static
859 void protocol_writeMessageBegin(zval* transport, zend_string* method_name, int32_t msgtype, int32_t seqID) {
860 zval args[3];
861 zval ret;
862 zval writeMessagefn;
863
864 ZVAL_STR_COPY(&args[0], method_name);
865 ZVAL_LONG(&args[1], msgtype);
866 ZVAL_LONG(&args[2], seqID);
867 ZVAL_NULL(&ret);
868 ZVAL_STRING(&writeMessagefn, "writeMessageBegin");
869
870 call_user_function(EG(function_table), transport, &writeMessagefn, &ret, 3, args);
871
872 zval_dtor(&writeMessagefn);
873 zval_dtor(&args[2]); zval_dtor(&args[1]); zval_dtor(&args[0]);
874 zval_dtor(&ret);
875 if (EG(exception)) {
876 zend_object *ex = EG(exception);
877 EG(exception) = nullptr;
878 throw PHPExceptionWrapper(ex);
879 }
880 }
881
882 static inline
883 bool ttype_is_int(int8_t t) {
884 return ((t == T_BYTE) || ((t >= T_I16) && (t <= T_I64)));
885 }
886 static inline
887 bool ttype_is_scalar(int8_t t) {
888 return !((t == T_STRUCT) || ( t== T_MAP) || (t == T_SET) || (t == T_LIST));
889 }
890
891 static inline
892 bool ttypes_are_compatible(int8_t t1, int8_t t2) {
893 // Integer types of different widths are considered compatible;
894 // otherwise the typeID must match.
895 return ((t1 == t2) || (ttype_is_int(t1) && ttype_is_int(t2)));
896 }
897
898 //is used to validate objects before serialization and after deserialization. For now, only required fields are validated.
899 static
900 void validate_thrift_object(zval* object) {
901 zend_class_entry* object_class_entry = Z_OBJCE_P(object);
902 zval* is_validate = zend_read_static_property(object_class_entry, "isValidate", sizeof("isValidate")-1, true);
903 if (is_validate) {
904 ZVAL_DEREF(is_validate);
905 }
906 zval* spec = zend_read_static_property(object_class_entry, "_TSPEC", sizeof("_TSPEC")-1, true);
907 if (spec) {
908 ZVAL_DEREF(spec);
909 }
910 HashPosition key_ptr;
911 zval* val_ptr;
912
913 if (is_validate && Z_TYPE_INFO_P(is_validate) == IS_TRUE) {
914 for (zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(spec), &key_ptr);
915 (val_ptr = zend_hash_get_current_data_ex(Z_ARRVAL_P(spec), &key_ptr)) != nullptr;
916 zend_hash_move_forward_ex(Z_ARRVAL_P(spec), &key_ptr)) {
917
918 zend_ulong fieldno;
919 if (zend_hash_get_current_key_ex(Z_ARRVAL_P(spec), nullptr, &fieldno, &key_ptr) != HASH_KEY_IS_LONG) {
920 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA);
921 return;
922 }
923 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
924
925 // field name
926 zval* zvarname = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
927 char* varname = Z_STRVAL_P(zvarname);
928
929 zval* is_required = zend_hash_str_find(fieldspec, "isRequired", sizeof("isRequired")-1);
930 zval rv;
931 zval* prop = zend_read_property(object_class_entry, object, varname, strlen(varname), false, &rv);
932
933 if (Z_TYPE_INFO_P(is_required) == IS_TRUE && Z_TYPE_P(prop) == IS_NULL) {
934 char errbuf[128];
935 snprintf(errbuf, 128, "Required field %s.%s is unset!", ZSTR_VAL(object_class_entry->name), varname);
936 throw_tprotocolexception(errbuf, INVALID_DATA);
937 }
938 }
939 }
940 }
941
942 static
943 void binary_deserialize_spec(zval* zthis, PHPInputTransport& transport, HashTable* spec) {
944 // SET and LIST have 'elem' => array('type', [optional] 'class')
945 // MAP has 'val' => array('type', [optiona] 'class')
946 zend_class_entry* ce = Z_OBJCE_P(zthis);
947 while (true) {
948 int8_t ttype = transport.readI8();
949 if (ttype == T_STOP) {
950 validate_thrift_object(zthis);
951 return;
952 }
953
954 int16_t fieldno = transport.readI16();
955 zval* val_ptr = zend_hash_index_find(spec, fieldno);
956 if (val_ptr != nullptr) {
957 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
958 // pull the field name
959 val_ptr = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
960 char* varname = Z_STRVAL_P(val_ptr);
961
962 // and the type
963 val_ptr = zend_hash_str_find(fieldspec, "type", sizeof("type")-1);
964 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
965 int8_t expected_ttype = Z_LVAL_P(val_ptr);
966
967 if (ttypes_are_compatible(ttype, expected_ttype)) {
968 zval rv;
969 ZVAL_UNDEF(&rv);
970
971 binary_deserialize(ttype, transport, &rv, fieldspec);
972 zend_update_property(ce, zthis, varname, strlen(varname), &rv);
973
974 zval_ptr_dtor(&rv);
975 } else {
976 skip_element(ttype, transport);
977 }
978 } else {
979 skip_element(ttype, transport);
980 }
981 }
982 }
983
984 static
985 void binary_serialize_spec(zval* zthis, PHPOutputTransport& transport, HashTable* spec) {
986
987 validate_thrift_object(zthis);
988
989 HashPosition key_ptr;
990 zval* val_ptr;
991
992 for (zend_hash_internal_pointer_reset_ex(spec, &key_ptr);
993 (val_ptr = zend_hash_get_current_data_ex(spec, &key_ptr)) != nullptr;
994 zend_hash_move_forward_ex(spec, &key_ptr)) {
995
996 zend_ulong fieldno;
997 if (zend_hash_get_current_key_ex(spec, nullptr, &fieldno, &key_ptr) != HASH_KEY_IS_LONG) {
998 throw_tprotocolexception("Bad keytype in TSPEC (expected 'long')", INVALID_DATA);
999 return;
1000 }
1001 HashTable* fieldspec = Z_ARRVAL_P(val_ptr);
1002
1003 // field name
1004 val_ptr = zend_hash_str_find(fieldspec, "var", sizeof("var")-1);
1005 char* varname = Z_STRVAL_P(val_ptr);
1006
1007 // thrift type
1008 val_ptr = zend_hash_str_find(fieldspec, "type", sizeof("type")-1);
1009 if (Z_TYPE_P(val_ptr) != IS_LONG) convert_to_long(val_ptr);
1010 int8_t ttype = Z_LVAL_P(val_ptr);
1011
1012 zval rv;
1013 zval* prop = zend_read_property(Z_OBJCE_P(zthis), zthis, varname, strlen(varname), false, &rv);
1014
1015 if (Z_TYPE_P(prop) == IS_REFERENCE){
1016 ZVAL_DEREF(prop);
1017 }
1018 if (Z_TYPE_P(prop) != IS_NULL) {
1019 transport.writeI8(ttype);
1020 transport.writeI16(fieldno);
1021 binary_serialize(ttype, transport, prop, fieldspec);
1022 }
1023 }
1024 transport.writeI8(T_STOP); // struct end
1025 }
1026
1027 // 6 params: $transport $method_name $ttype $request_struct $seqID $strict_write
1028 PHP_FUNCTION(thrift_protocol_write_binary) {
1029 zval *protocol;
1030 zval *request_struct;
1031 zend_string *method_name;
1032 long msgtype, seqID;
1033 zend_bool strict_write;
1034
1035 if (zend_parse_parameters_ex(ZEND_PARSE_PARAMS_QUIET, ZEND_NUM_ARGS(), "oSlolb",
1036 &protocol, &method_name, &msgtype,
1037 &request_struct, &seqID, &strict_write) == FAILURE) {
1038 return;
1039 }
1040
1041 try {
1042 zval* spec = zend_read_static_property(Z_OBJCE_P(request_struct), "_TSPEC", sizeof("_TSPEC")-1, true);
1043 if (spec) {
1044 ZVAL_DEREF(spec);
1045 }
1046
1047 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
1048 throw_tprotocolexception("Attempt serialize from non-Thrift object", INVALID_DATA);
1049 }
1050
1051 PHPOutputTransport transport(protocol);
1052 protocol_writeMessageBegin(protocol, method_name, (int32_t) msgtype, (int32_t) seqID);
1053 binary_serialize_spec(request_struct, transport, Z_ARRVAL_P(spec));
1054 transport.flush();
1055
1056 } catch (const PHPExceptionWrapper& ex) {
1057 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1058 zval myex;
1059 ZVAL_COPY(&myex, ex);
1060 zend_throw_exception_object(&myex);
1061 RETURN_NULL();
1062 } catch (const std::exception& ex) {
1063 throw_zend_exception_from_std_exception(ex);
1064 RETURN_NULL();
1065 }
1066 }
1067
1068
1069 // 4 params: $transport $response_Typename $strict_read $buffer_size
1070 PHP_FUNCTION(thrift_protocol_read_binary) {
1071 zval *protocol;
1072 zend_string *obj_typename;
1073 zend_bool strict_read;
1074 size_t buffer_size = 8192;
1075
1076 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol, &obj_typename, &strict_read, &buffer_size) == FAILURE) {
1077 return;
1078 }
1079
1080 try {
1081 PHPInputTransport transport(protocol, buffer_size);
1082 int8_t messageType = 0;
1083 int32_t sz = transport.readI32();
1084
1085 if (sz < 0) {
1086 // Check for correct version number
1087 int32_t version = sz & VERSION_MASK;
1088 if (version != VERSION_1) {
1089 throw_tprotocolexception("Bad version identifier", BAD_VERSION);
1090 }
1091 messageType = (sz & 0x000000ff);
1092 int32_t namelen = transport.readI32();
1093 // skip the name string and the sequence ID, we don't care about those
1094 transport.skip(namelen + 4);
1095 } else {
1096 if (strict_read) {
1097 throw_tprotocolexception("No version identifier... old protocol client in strict mode?", BAD_VERSION);
1098 } else {
1099 // Handle pre-versioned input
1100 transport.skip(sz); // skip string body
1101 messageType = transport.readI8();
1102 transport.skip(4); // skip sequence number
1103 }
1104 }
1105
1106 if (messageType == T_EXCEPTION) {
1107 zval ex;
1108 createObject("\\Thrift\\Exception\\TApplicationException", &ex);
1109 zval* spec = zend_read_static_property(Z_OBJCE(ex), "_TSPEC", sizeof("_TPSEC")-1, false);
1110 ZVAL_DEREF(spec);
1111 if (EG(exception)) {
1112 zend_object *ex = EG(exception);
1113 EG(exception) = nullptr;
1114 throw PHPExceptionWrapper(ex);
1115 }
1116 binary_deserialize_spec(&ex, transport, Z_ARRVAL_P(spec));
1117 throw PHPExceptionWrapper(&ex);
1118 }
1119
1120 createObject(ZSTR_VAL(obj_typename), return_value);
1121 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, true);
1122 if (spec) {
1123 ZVAL_DEREF(spec);
1124 }
1125 if (!spec || Z_TYPE_P(spec) != IS_ARRAY) {
1126 throw_tprotocolexception("Attempt deserialize to non-Thrift object", INVALID_DATA);
1127 }
1128 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
1129 } catch (const PHPExceptionWrapper& ex) {
1130 // ex will be destructed, so copy to a zval that zend_throw_exception_object can ownership of
1131 zval myex;
1132 ZVAL_COPY(&myex, ex);
1133 zval_dtor(return_value);
1134 zend_throw_exception_object(&myex);
1135 RETURN_NULL();
1136 } catch (const std::exception& ex) {
1137 throw_zend_exception_from_std_exception(ex);
1138 RETURN_NULL();
1139 }
1140 }
1141
1142 // 4 params: $transport $response_Typename $strict_read $buffer_size
1143 PHP_FUNCTION(thrift_protocol_read_binary_after_message_begin) {
1144 zval *protocol;
1145 zend_string *obj_typename;
1146 zend_bool strict_read;
1147 size_t buffer_size = 8192;
1148
1149 if (zend_parse_parameters(ZEND_NUM_ARGS(), "oSb|l", &protocol, &obj_typename, &strict_read, &buffer_size) == FAILURE) {
1150 return;
1151 }
1152
1153 try {
1154 PHPInputTransport transport(protocol, buffer_size);
1155
1156 createObject(ZSTR_VAL(obj_typename), return_value);
1157 zval* spec = zend_read_static_property(Z_OBJCE_P(return_value), "_TSPEC", sizeof("_TSPEC")-1, false);
1158 ZVAL_DEREF(spec);
1159 binary_deserialize_spec(return_value, transport, Z_ARRVAL_P(spec));
1160 } catch (const PHPExceptionWrapper& ex) {
1161 // ex will be destructed, so copy to a zval that zend_throw_exception_object can take ownership of
1162 zval myex;
1163 ZVAL_COPY(&myex, ex);
1164 zend_throw_exception_object(&myex);
1165 RETURN_NULL();
1166 } catch (const std::exception& ex) {
1167 throw_zend_exception_from_std_exception(ex);
1168 RETURN_NULL();
1169 }
1170 }
1171
1172 #endif /* PHP_VERSION_ID >= 70000 */