diff --git a/Changelog.md b/Changelog.md index 9df76702b5..1b63608cd4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,7 +5,72 @@ All changes to phpredis will be documented in this file. We're basing this format on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [5.3.6] - 2021-01-17 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.6), [PECL](https:/pecl.php.net/package/redis/5.3.6)) +## [Unreleased] + +## [5.3.7] - 2021-02-15 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7), [PECL](https://pecl.php.net/package/redis/5.3.7)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +*There were no changes between 5.3.7 and 5.3.7RC2* + +## [5.3.7RC2] - 2021-02-12 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7RC2), [PECL](https://pecl.php.net/package/redis/5.3.7RC2)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +*There were no changes between 5.3.7RC2 and 5.3.7RC1* + +## [5.3.7RC1] - 2021-02-02 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.7RC1), [PECL](https://pecl.php.net/package/redis/5.3.7RC1)) + +### Sponsors :sparkling_heart: + +- [Audiomack](https://audiomack.com) +- [Open LMS](https://openlms.net/) +- [BlueHost](https://bluehost.com) +- [Object Cache Pro for WordPress](https://objectcache.pro/) +- [Avtandil Kikabidze](https://github.com/akalongman) +- [Zaher Ghaibeh](https://github.com/zaherg) +- [BatchLabs](https://batch.com) +- [Stackhero](https://github.com/stackhero-io) +- [Florian Levis](https://github.com/Gounlaf) +- [Luis Zárate](https://github.com/jlzaratec) + +### Fixed + +- Fix RedisArray::[hsz]scan and tests + [08a9d5db](https://github.com/phpredis/phpredis/commit/08a9d5db), + [0264de18](https://github.com/phpredis/phpredis/commit/0264de18), + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)), + ([Michael Grunder](https://github.com/michael-grunder)) +- Fix RedisArray::scan + [8689ab1c](https://github.com/phpredis/phpredis/commit/8689ab1c) + ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) +- Fix LZF decompression logic + [0719c1ec](https://github.com/phpredis/phpredis/commit/0719c1ec) + ([Michael Grunder](https://github.com/michael-grunder)) + +## [5.3.6] - 2021-01-17 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.6), [PECL](https://pecl.php.net/package/redis/5.3.6)) ### Sponsors :sparkling_heart: @@ -26,7 +91,7 @@ and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm [d2f2a7d9](https://github.com/phpredis/phpredis/commit/d2f2a7d9) ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) -## [5.3.5] - 2021-12-18 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5), [PECL](https:/pecl.php.net/package/redis/5.3.5)) +## [5.3.5] - 2021-12-18 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5), [PECL](https://pecl.php.net/package/redis/5.3.5)) ### Sponsors :sparkling_heart: @@ -46,7 +111,7 @@ and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm - Fixed typo in cluster_scan_resp [44affad2](https://github.com/phpredis/phpredis/commit/44affad2) -## [5.3.5RC1] - 2021-11-16 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5RC1), [PECL](https:/pecl.php.net/package/redis/5.3.5RC1)) +## [5.3.5RC1] - 2021-11-16 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.5RC1), [PECL](https://pecl.php.net/package/redis/5.3.5RC1)) ### Sponsors :sparkling_heart: @@ -172,7 +237,7 @@ and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm [ed283e1ab](https://github.com/phpredis/phpredis/commit/ed283e1ab), ([Pavlo Yatsukhnenko](https://github.com/yatsukhnenko)) -## [5.3.4] - 2021-03-24 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.4), [PECL](https:/pecl.php.net/package/redis/5.3.4)) +## [5.3.4] - 2021-03-24 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.4), [PECL](https://pecl.php.net/package/redis/5.3.4)) ### Sponsors :sparkling_heart: @@ -199,7 +264,7 @@ and PhpRedis adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm [9b986bf8](https://github.com/phpredis/phpredis/commit/9b986bf81859f5a5983cd148cb15ee6ce292d288) ([Michael Grunder](https://github.com/michael-grunder)) -## [5.3.3] - 2021-02-01 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.3), [PECL](https:/pecl.php.net/package/redis/5.3.3)) +## [5.3.3] - 2021-02-01 ([GitHub](https://github.com/phpredis/phpredis/releases/5.3.3), [PECL](https://pecl.php.net/package/redis/5.3.3)) ### Sponsors :sparkling_heart: diff --git a/common.h b/common.h index 2042f5091e..83f01dc63d 100644 --- a/common.h +++ b/common.h @@ -771,4 +771,15 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_xdel, 0, 0, 2) ZEND_ARG_ARRAY_INFO(0, arr_ids, 0) ZEND_END_ARG_INFO() +/** + * Argument info for key scanning + */ +ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) + ZEND_ARG_INFO(0, str_key) + ZEND_ARG_INFO(1, i_iterator) + ZEND_ARG_INFO(0, str_pattern) + ZEND_ARG_INFO(0, i_count) +ZEND_END_ARG_INFO() + + #endif diff --git a/library.c b/library.c index d67adf6183..c1414cb716 100644 --- a/library.c +++ b/library.c @@ -2941,27 +2941,22 @@ redis_uncompress(RedisSock *redis_sock, char **dst, size_t *dstlen, const char * case REDIS_COMPRESSION_LZF: #ifdef HAVE_REDIS_LZF { - char *data; - int i; + char *data = NULL; uint32_t res; + int i; if (len == 0) break; - /* start from two-times bigger buffer and - * increase it exponentially if needed */ + /* Grow our buffer until we succeed or get a non E2BIG error */ errno = E2BIG; for (i = 2; errno == E2BIG; i *= 2) { - data = emalloc(i * len); - if ((res = lzf_decompress(src, len, data, i * len)) == 0) { - /* errno != E2BIG will brake for loop */ - efree(data); - continue; + data = erealloc(data, len * i); + if ((res = lzf_decompress(src, len, data, len * i)) > 0) { + *dst = data; + *dstlen = res; + return 1; } - - *dst = data; - *dstlen = res; - return 1; } efree(data); diff --git a/package.xml b/package.xml index 13a86933b7..abd4a305d0 100644 --- a/package.xml +++ b/package.xml @@ -27,10 +27,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> n.favrefelix@gmail.com no - 2022-01-17 + 2022-02-15 - 5.3.6 - 5.3.6 + 5.3.7 + 5.3.7 stable @@ -49,11 +49,23 @@ http://pear.php.net/dtd/package-2.0.xsd"> BatchLabs - https://batch.com Luis Zarate - https://github.com/jlzaratec + phpredis 5.3.7 + + - There were no changes between 5.3.7 and 5.3.7RC2. + --- - phpredis 5.3.6 + phpredis 5.3.7RC2 - - Fix a segfault in RedisArray::del [d2f2a7d9] (Pavlo Yatsukhnenko) + - There were no changes between 5.3.7RC2 and 5.3.7RC1. + + --- + + phpredis 5.3.7RC1 + + - Fix RedisArray::[hsz]scan and tests [08a9d5db, 0264de18] (Pavlo Yatsukhnenko, Michael Grunder) + - Fix RedisArray::scan [8689ab1c] (Pavlo Yatsukhnenko) + - Fix LZF decompression logic [0719c1ec] (Michael Grunder) @@ -131,6 +143,66 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + stablestable + 5.3.75.3.7 + 2022-02-15 + + --- Sponsors --- + + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec + + phpredis 5.3.7 + + - There were no changes between 5.3.7 and 5.3.7RC2. + + --- + + phpredis 5.3.7RC2 + + - There were no changes between 5.3.7RC2 and 5.3.7RC1. + + --- + + phpredis 5.3.7RC1 + + - Fix RedisArray::[hsz]scan and tests [08a9d5db, 0264de18] (Pavlo Yatsukhnenko, Michael Grunder) + - Fix RedisArray::scan [8689ab1c] (Pavlo Yatsukhnenko) + - Fix LZF decompression logic [0719c1ec] (Michael Grunder) + + + + + stablestable + 5.3.65.3.6 + 2022-01-17 + + --- Sponsors --- + + Audiomack - https://audiomack.com + Open LMS - https://openlms.net + BlueHost - https://bluehost.com + Object Cache Pro for WordPress - https://objectcache.pro + Avtandil Kikabidze - https://github.com/akalongman + Zaher Ghaibeh - https://github.com/zaherg + BatchLabs - https://batch.com + Luis Zarate - https://github.com/jlzaratec + + --- + + phpredis 5.3.6 + + - Fix a segfault in RedisArray::del [d2f2a7d9] (Pavlo Yatsukhnenko) + + + stablestable 5.3.55.3.5 diff --git a/php_redis.h b/php_redis.h index 94f1afe424..9b639bcd57 100644 --- a/php_redis.h +++ b/php_redis.h @@ -23,7 +23,7 @@ #define PHP_REDIS_H /* phpredis version */ -#define PHP_REDIS_VERSION "5.3.6" +#define PHP_REDIS_VERSION "5.3.7" PHP_METHOD(Redis, __construct); PHP_METHOD(Redis, __destruct); diff --git a/redis.c b/redis.c index 9c3c75f572..d63beecc14 100644 --- a/redis.c +++ b/redis.c @@ -243,16 +243,6 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 1) ZEND_ARG_INFO(0, i_count) ZEND_END_ARG_INFO() -/** - * Argument info for key scanning - */ -ZEND_BEGIN_ARG_INFO_EX(arginfo_kscan, 0, 0, 2) - ZEND_ARG_INFO(0, str_key) - ZEND_ARG_INFO(1, i_iterator) - ZEND_ARG_INFO(0, str_pattern) - ZEND_ARG_INFO(0, i_count) -ZEND_END_ARG_INFO() - static zend_function_entry redis_functions[] = { PHP_ME(Redis, __construct, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(Redis, __destruct, arginfo_void, ZEND_ACC_PUBLIC) diff --git a/redis_array.c b/redis_array.c index 16142f9f3f..2cf4d9e2cb 100644 --- a/redis_array.c +++ b/redis_array.c @@ -97,6 +97,13 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_flush, 0, 0, 0) ZEND_ARG_INFO(0, async) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_scan, 0, 0, 2) + ZEND_ARG_INFO(1, iterator) + ZEND_ARG_INFO(0, node) + ZEND_ARG_INFO(0, pattern) + ZEND_ARG_INFO(0, count) +ZEND_END_ARG_INFO() + zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, __call, arginfo_call, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, __construct, arginfo_ctor, ZEND_ACC_PUBLIC) @@ -114,6 +121,7 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, flushall, arginfo_flush, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, flushdb, arginfo_flush, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, getOption, arginfo_getopt, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, hscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, info, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, keys, arginfo_keys, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, mget, arginfo_mget, ZEND_ACC_PUBLIC) @@ -121,10 +129,13 @@ zend_function_entry redis_array_functions[] = { PHP_ME(RedisArray, multi, arginfo_multi, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, ping, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, save, arginfo_void, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, scan, arginfo_scan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, select, arginfo_select, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, setOption,arginfo_setopt, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, sscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unlink, arginfo_void, ZEND_ACC_PUBLIC) PHP_ME(RedisArray, unwatch, arginfo_void, ZEND_ACC_PUBLIC) + PHP_ME(RedisArray, zscan, arginfo_kscan, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, delete, del, arginfo_del, ZEND_ACC_PUBLIC) PHP_MALIAS(RedisArray, getMultiple, mget, arginfo_mget, ZEND_ACC_PUBLIC) PHP_FE_END @@ -1195,6 +1206,86 @@ PHP_METHOD(RedisArray, unlink) { ra_generic_del(INTERNAL_FUNCTION_PARAM_PASSTHRU, "UNLINK", sizeof("UNLINK") - 1); } +static void +ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAMETERS, const char *kw, int kw_len) +{ + RedisArray *ra; + zend_string *key, *pattern = NULL; + zval *object, *redis_inst, *z_iter, z_fun, z_args[4]; + zend_long count = 0; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "OSz/|S!l", + &object, redis_array_ce, &key, &z_iter, &pattern, &count) == FAILURE) { + RETURN_FALSE; + } + + if ((ra = redis_array_get(object)) == NULL) { + RETURN_FALSE; + } + + if ((redis_inst = ra_find_node(ra, ZSTR_VAL(key), ZSTR_LEN(key), NULL)) == NULL) { + php_error_docref(NULL, E_ERROR, "Could not find any redis servers for this key."); + RETURN_FALSE; + } + + ZVAL_STR(&z_args[0], key); + ZVAL_NEW_REF(&z_args[1], z_iter); + if (pattern) ZVAL_STR(&z_args[2], pattern); + ZVAL_LONG(&z_args[3], count); + + ZVAL_STRINGL(&z_fun, kw, kw_len); + call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, ZEND_NUM_ARGS(), z_args); + zval_dtor(&z_fun); + + ZVAL_ZVAL(z_iter, &z_args[1], 0, 1); +} + +PHP_METHOD(RedisArray, hscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "HSCAN", sizeof("HSCAN") - 1); +} + +PHP_METHOD(RedisArray, sscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "SSCAN", sizeof("SSCAN") - 1); +} + +PHP_METHOD(RedisArray, zscan) +{ + ra_generic_scan_cmd(INTERNAL_FUNCTION_PARAM_PASSTHRU, "ZSCAN", sizeof("ZSCAN") - 1); +} + +PHP_METHOD(RedisArray, scan) +{ + RedisArray *ra; + zend_string *host, *pattern = NULL; + zval *object, *redis_inst, *z_iter, z_fun, z_args[3]; + zend_long count = 0; + + if (zend_parse_method_parameters(ZEND_NUM_ARGS(), getThis(), "Oz/S|S!l", + &object, redis_array_ce, &z_iter, &host, &pattern, &count) == FAILURE) { + RETURN_FALSE; + } + + if ((ra = redis_array_get(object)) == NULL) { + RETURN_FALSE; + } + + if ((redis_inst = ra_find_node_by_name(ra, host)) == NULL) { + RETURN_FALSE; + } + + ZVAL_NEW_REF(&z_args[0], z_iter); + if (pattern) ZVAL_STR(&z_args[1], pattern); + ZVAL_LONG(&z_args[2], count); + + ZVAL_STRING(&z_fun, "SCAN"); + call_user_function(&redis_ce->function_table, redis_inst, &z_fun, return_value, ZEND_NUM_ARGS() - 1, z_args); + zval_dtor(&z_fun); + + ZVAL_ZVAL(z_iter, &z_args[0], 0, 1); +} + PHP_METHOD(RedisArray, multi) { zval *object; diff --git a/redis_array.h b/redis_array.h index 805442aac9..7cdc6057c2 100644 --- a/redis_array.h +++ b/redis_array.h @@ -8,35 +8,37 @@ #endif #include "common.h" -PHP_METHOD(RedisArray, __construct); PHP_METHOD(RedisArray, __call); +PHP_METHOD(RedisArray, __construct); +PHP_METHOD(RedisArray, _continuum); +PHP_METHOD(RedisArray, _distributor); +PHP_METHOD(RedisArray, _function); PHP_METHOD(RedisArray, _hosts); -PHP_METHOD(RedisArray, _target); PHP_METHOD(RedisArray, _instance); -PHP_METHOD(RedisArray, _function); -PHP_METHOD(RedisArray, _distributor); PHP_METHOD(RedisArray, _rehash); -PHP_METHOD(RedisArray, _continuum); - -PHP_METHOD(RedisArray, select); -PHP_METHOD(RedisArray, info); -PHP_METHOD(RedisArray, ping); -PHP_METHOD(RedisArray, flushdb); +PHP_METHOD(RedisArray, _target); +PHP_METHOD(RedisArray, bgsave); +PHP_METHOD(RedisArray, del); +PHP_METHOD(RedisArray, discard); +PHP_METHOD(RedisArray, exec); PHP_METHOD(RedisArray, flushall); +PHP_METHOD(RedisArray, flushdb); +PHP_METHOD(RedisArray, getOption); +PHP_METHOD(RedisArray, hscan); +PHP_METHOD(RedisArray, info); +PHP_METHOD(RedisArray, keys); PHP_METHOD(RedisArray, mget); PHP_METHOD(RedisArray, mset); -PHP_METHOD(RedisArray, del); -PHP_METHOD(RedisArray, unlink); -PHP_METHOD(RedisArray, keys); -PHP_METHOD(RedisArray, getOption); -PHP_METHOD(RedisArray, setOption); -PHP_METHOD(RedisArray, save); -PHP_METHOD(RedisArray, bgsave); - PHP_METHOD(RedisArray, multi); -PHP_METHOD(RedisArray, exec); -PHP_METHOD(RedisArray, discard); +PHP_METHOD(RedisArray, ping); +PHP_METHOD(RedisArray, save); +PHP_METHOD(RedisArray, scan); +PHP_METHOD(RedisArray, select); +PHP_METHOD(RedisArray, setOption); +PHP_METHOD(RedisArray, sscan); +PHP_METHOD(RedisArray, unlink); PHP_METHOD(RedisArray, unwatch); +PHP_METHOD(RedisArray, zscan); typedef struct { uint32_t value; diff --git a/tests/RedisArrayTest.php b/tests/RedisArrayTest.php index a852688625..5a20d271c7 100644 --- a/tests/RedisArrayTest.php +++ b/tests/RedisArrayTest.php @@ -166,6 +166,43 @@ public function testKeyDistributor() } } + /* Scan a whole key and return the overall result */ + protected function execKeyScan($cmd, $key) { + $res = []; + + $it = NULL; + do { + $chunk = $this->ra->$cmd($key, $it); + foreach ($chunk as $field => $value) { + $res[$field] = $value; + } + } while ($it !== 0); + + return $res; + } + + public function testKeyScanning() { + $h_vals = ['foo' => 'bar', 'baz' => 'bop']; + $z_vals = ['one' => 1, 'two' => 2, 'three' => 3]; + $s_vals = ['mem1', 'mem2', 'mem3']; + + $this->ra->del(['scan-hash', 'scan-set', 'scan-zset']); + + $this->ra->hMSet('scan-hash', $h_vals); + foreach ($z_vals as $k => $v) + $this->ra->zAdd('scan-zset', $v, $k); + $this->ra->sAdd('scan-set', ...$s_vals); + + $s_scan = $this->execKeyScan('sScan', 'scan-set'); + $this->assertTrue(count(array_diff_key(array_flip($s_vals), array_flip($s_scan))) == 0); + + $this->assertEquals($h_vals, $this->execKeyScan('hScan', 'scan-hash')); + + $z_scan = $this->execKeyScan('zScan', 'scan-zset'); + $this->assertTrue(count($z_scan) == count($z_vals) && + count(array_diff_key($z_vals, $z_scan)) == 0 && + array_sum($z_scan) == array_sum($z_vals)); + } } class Redis_Rehashing_Test extends TestSuite diff --git a/tests/RedisTest.php b/tests/RedisTest.php index 2a85f620fe..7484849d38 100644 --- a/tests/RedisTest.php +++ b/tests/RedisTest.php @@ -4584,6 +4584,14 @@ public function testCompressionLZF() if (!defined('Redis::COMPRESSION_LZF')) { $this->markTestSkipped(); } + + /* Don't crash on improperly compressed LZF data */ + $payload = 'not-actually-lzf-compressed'; + $this->redis->set('badlzf', $payload); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_LZF); + $this->assertEquals($payload, $this->redis->get('badlzf')); + $this->redis->setOption(Redis::OPT_COMPRESSION, Redis::COMPRESSION_NONE); + $this->checkCompression(Redis::COMPRESSION_LZF, 0); }