diff --git a/ext/sockets/php_sockets.h b/ext/sockets/php_sockets.h index 89c613ba43074..7342e7a2f24f3 100644 --- a/ext/sockets/php_sockets.h +++ b/ext/sockets/php_sockets.h @@ -76,6 +76,9 @@ typedef struct { } php_socket; extern PHP_SOCKETS_API zend_class_entry *socket_ce; +#ifdef SO_SPLICE +extern PHP_SOCKETS_API zend_class_entry *socket_so_splice_ce; +#endif static inline php_socket *socket_from_obj(zend_object *obj) { return (php_socket *)((char *)(obj) - XtOffsetOf(php_socket, std)); diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 2d8b55c6b97ba..2d02335d684d5 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -121,6 +121,10 @@ static PHP_RSHUTDOWN_FUNCTION(sockets); zend_class_entry *socket_ce; static zend_object_handlers socket_object_handlers; +#ifdef SO_SPLICE +zend_class_entry *socket_so_splice_ce; +#endif + static zend_object *socket_create_object(zend_class_entry *class_type) { php_socket *intern = zend_object_alloc(sizeof(php_socket), class_type); @@ -482,6 +486,10 @@ static PHP_MINIT_FUNCTION(sockets) socket_object_handlers.get_gc = socket_get_gc; socket_object_handlers.compare = zend_objects_not_comparable; +#if defined(SO_SPLICE) + socket_so_splice_ce = register_class_SocketSoSplice(); +#endif + address_info_ce = register_class_AddressInfo(); address_info_ce->create_object = address_info_create_object; address_info_ce->default_object_handlers = &address_info_object_handlers; @@ -2301,6 +2309,68 @@ PHP_FUNCTION(socket_set_option) } #endif +#ifdef SO_SPLICE + case SO_SPLICE: { + if (Z_TYPE_P(arg4) != IS_OBJECT || !instanceof_function(Z_OBJCE_P(arg4), socket_so_splice_ce)) { + zend_argument_type_error(4, "must be of type SocketSoSplice, %s given", zend_zval_value_name(arg4)); + RETURN_THROWS(); + } + + zend_object *so_splice_obj = Z_OBJ_P(arg4); + zval tmpA, tmpB, tmpC; + + zval *socket = zend_read_property(socket_so_splice_ce, so_splice_obj, "socket", strlen("socket"), false, &tmpA); + if (Z_TYPE_P(socket) == IS_NULL) { + zend_argument_type_error(4, "socket cannot be null"); + RETURN_THROWS(); + } + zval *max = zend_read_property(socket_so_splice_ce, so_splice_obj, "max", strlen("max"), false, &tmpB); + zval *array = zend_read_property(socket_so_splice_ce, so_splice_obj, "time", strlen("time"), false, &tmpC); + if (Z_TYPE_P(array) == IS_NULL) { + zend_argument_type_error(4, "time cannot be null"); + RETURN_THROWS(); + } + + php_socket *php_sock = Z_SOCKET_P(socket); + zend_long php_max = Z_LVAL_P(max); + zend_array *php_arr = Z_ARRVAL_P(array); + + ENSURE_SOCKET_VALID(php_sock); + + if (php_max < 0) { + zend_argument_value_error(4, "\"max\" key must be greater than or equal to 0"); + RETURN_THROWS(); + } + + zval *tv_sec; + zval *tv_usec; + + if ((tv_sec = zend_hash_str_find(php_arr, "sec", strlen("sec"))) == NULL) { + zend_argument_value_error(4, "time must have key \"sec\""); + RETURN_THROWS(); + } + if ((tv_usec = zend_hash_str_find(php_arr, "usec", strlen("usec"))) == NULL) { + zend_argument_value_error(4, "time must have key \"usec\""); + RETURN_THROWS(); + } + + struct splice s; + s.sp_fd = (int)php_sock->bsd_socket; + s.sp_max = (off_t)php_max; + if (Z_LVAL_P(tv_sec) > 999999) { + s.sp_idle.tv_sec = Z_LVAL_P(tv_sec) + (Z_LVAL_P(tv_usec) / 1000000); + s.sp_idle.tv_usec = Z_LVAL_P(tv_usec) % 1000000; + } else { + s.sp_idle.tv_sec = Z_LVAL_P(tv_sec); + s.sp_idle.tv_usec = Z_LVAL_P(tv_usec); + } + + opt_ptr = &s; + optlen = sizeof(s); + break; + } +#endif + default: default_case: ov = zval_get_long(arg4); diff --git a/ext/sockets/sockets.stub.php b/ext/sockets/sockets.stub.php index d647f46b80dca..f02f4de2188be 100644 --- a/ext/sockets/sockets.stub.php +++ b/ext/sockets/sockets.stub.php @@ -1907,6 +1907,13 @@ * @cvalue SO_NOSIGPIPE */ const SO_NOSIGPIPE = UNKNOWN; +#if defined(SO_SPLICE) +/** + * @var int + * @cvalue SO_SPLICE + */ +const SO_SPLICE = UNKNOWN; +#endif #endif #if defined(TCP_QUICKACK) /** @@ -2038,6 +2045,15 @@ final class AddressInfo { } +#ifdef SO_SPLICE +final class SocketSoSplice +{ + public \Socket $socket; + public int $max; + public array $time; +} +#endif + function socket_select(?array &$read, ?array &$write, ?array &$except, ?int $seconds, int $microseconds = 0): int|false {} function socket_create_listen(int $port, int $backlog = SOMAXCONN): Socket|false {} diff --git a/ext/sockets/sockets_arginfo.h b/ext/sockets/sockets_arginfo.h index 45714fb285174..f51481048ca80 100644 --- a/ext/sockets/sockets_arginfo.h +++ b/ext/sockets/sockets_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 42d486d2666d23569e70860e2b1ef203161792b3 */ + * Stub hash: 1350cf0464d5dbcf3bec0ad0f26445da8153aedc */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_socket_select, 0, 4, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(1, read, IS_ARRAY, 1) @@ -1057,6 +1057,9 @@ static void register_sockets_symbols(int module_number) #if defined(SO_NOSIGPIPE) REGISTER_LONG_CONSTANT("SO_NOSIGPIPE", SO_NOSIGPIPE, CONST_PERSISTENT); #endif +#if defined(SO_NOSIGPIPE) && defined(SO_SPLICE) + REGISTER_LONG_CONSTANT("SO_SPLICE", SO_SPLICE, CONST_PERSISTENT); +#endif #if defined(TCP_QUICKACK) REGISTER_LONG_CONSTANT("TCP_QUICKACK", TCP_QUICKACK, CONST_PERSISTENT); #endif @@ -1129,3 +1132,34 @@ static zend_class_entry *register_class_AddressInfo(void) return class_entry; } + +#if defined(SO_SPLICE) +static zend_class_entry *register_class_SocketSoSplice(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "SocketSoSplice", NULL); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL); + + zval property_socket_default_value; + ZVAL_UNDEF(&property_socket_default_value); + zend_string *property_socket_name = zend_string_init("socket", sizeof("socket") - 1, 1); + zend_string *property_socket_class_Socket = zend_string_init("Socket", sizeof("Socket")-1, 1); + zend_declare_typed_property(class_entry, property_socket_name, &property_socket_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_CLASS(property_socket_class_Socket, 0, 0)); + zend_string_release(property_socket_name); + + zval property_max_default_value; + ZVAL_UNDEF(&property_max_default_value); + zend_string *property_max_name = zend_string_init("max", sizeof("max") - 1, 1); + zend_declare_typed_property(class_entry, property_max_name, &property_max_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(property_max_name); + + zval property_time_default_value; + ZVAL_UNDEF(&property_time_default_value); + zend_string *property_time_name = zend_string_init("time", sizeof("time") - 1, 1); + zend_declare_typed_property(class_entry, property_time_name, &property_time_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_ARRAY)); + zend_string_release(property_time_name); + + return class_entry; +} +#endif diff --git a/ext/sockets/tests/socket_set_option_so_splice.phpt b/ext/sockets/tests/socket_set_option_so_splice.phpt new file mode 100644 index 0000000000000..95375219a52ed --- /dev/null +++ b/ext/sockets/tests/socket_set_option_so_splice.phpt @@ -0,0 +1,101 @@ +--TEST-- +SO_SPLICE usage with socket_set_option +--EXTENSIONS-- +sockets +--SKIPIF-- + +--FILE-- +getMessage(), PHP_EOL; +} + +try { + socket_set_option($socket, SOL_SOCKET, SO_SPLICE, new badClass()); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $s = new SocketSoSplice(); + $s->max = -1; + $s->socket = $todrain; + $s->time = ["sec" => 0, "usec" => 0]; + socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s); +} catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + + +try { + $s = new SocketSoSplice(); + socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $s = new SocketSoSplice(); + $s->max = 1024; + $s->socket = $todrain; + $s->time = ["invalid" => 0, "usec" => 0]; + socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $s = new SocketSoSplice(); + $s->max = 1024; + $s->socket = $todrain; + $s->time = ["sec" => 0, "nosec" => 0]; + socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s); +} catch (Error $e) { + echo $e->getMessage(), PHP_EOL; +} + +socket_bind($todrain, '127.0.0.1', 0); +socket_listen($todrain, 1); + +socket_getsockname($todrain, $addr, $port); +socket_connect($socket, $addr, $port); +$peer = socket_accept($todrain); + +$s1 = new SocketSoSplice(); +$s1->max = 5; +$s1->socket = $todrain; +$s1->time = ["sec" => 1, "usec" => 1]; + +$s2 = new SocketSoSplice(); +$s2->max = 5; +$s2->socket = $socket; +$s2->time = ["sec" => 1, "usec" => 1]; +var_dump(socket_set_option($socket, SOL_SOCKET, SO_SPLICE, $s1)); +var_dump(socket_set_option($todrain, SOL_SOCKET, SO_SPLICE, $s2)); +socket_write($socket, "HELLO", 5); +var_dump(socket_read($peer, 5, PHP_NORMAL_READ)); + +socket_close($peer); +socket_close($todrain); +socket_close($socket); + +?> +--EXPECT-- +socket_set_option(): Argument #4 ($value) must be of type SocketSoSplice, Socket given +socket_set_option(): Argument #4 ($value) must be of type SocketSoSplice, badClass given +socket_set_option(): Argument #4 ($value) "max" key must be greater than or equal to 0 +Typed property SocketSoSplice::$socket must not be accessed before initialization +socket_set_option(): Argument #4 ($value) time must have key "sec" +socket_set_option(): Argument #4 ($value) time must have key "usec"