diff --git a/src/Protocols/Redis.php b/src/Protocols/Redis.php new file mode 100644 index 0000000..f07be4c --- /dev/null +++ b/src/Protocols/Redis.php @@ -0,0 +1,172 @@ + + * @copyright Copyright (c) 2018-2025 Localzet Group + * @license https://www.gnu.org/licenses/agpl-3.0 GNU Affero General Public License v3.0 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * For any questions, please contact + */ + +namespace localzet\Server\Protocols; + +use localzet\Server\Connection\ConnectionInterface; +use function count; +use function is_array; +use function strlen; +use function strpos; +use function substr; + +/** + * Протокол Redis. + */ +class Redis implements ProtocolInterface +{ + /** @inheritdoc */ + public static function input(string $buffer, ConnectionInterface $connection): int + { + $type = $buffer[0]; + $pos = strpos($buffer, "\r\n"); + if (false === $pos) { + return 0; + } + switch ($type) { + case ':': + case '+': + case '-': + return $pos + 2; + case '$': + if (str_starts_with($buffer, '$-1')) { + return 5; + } + return $pos + 4 + (int)substr($buffer, 1, $pos); + case '*': + if (str_starts_with($buffer, '*-1')) { + return 5; + } + $count = (int)substr($buffer, 1, $pos - 1); + while ($count--) { + $next_pos = strpos($buffer, "\r\n", $pos + 2); + if (!$next_pos) { + return 0; + } + $sub_type = $buffer[$pos + 2]; + switch ($sub_type) { + case ':': + case '+': + case '-': + $pos = $next_pos; + break; + case '$': + if ($pos + 2 === strpos($buffer, '$-1', $pos)) { + $pos = $next_pos; + break; + } + $length = (int)substr($buffer, $pos + 3, $next_pos - $pos - 3); + $pos = $next_pos + $length + 2; + if (strlen($buffer) < $pos) { + return 0; + } + break; + default: + return strlen($buffer); + } + } + return $pos + 2; + default: + return strlen($buffer); + } + } + + /** @inheritdoc */ + public static function encode(mixed $data, ConnectionInterface $connection): string + { + $cmd = ''; + $count = count($data); + foreach ($data as $item) { + if (is_array($item)) { + $count += count($item) - 1; + foreach ($item as $str) { + $cmd .= '$' . strlen($str) . "\r\n$str\r\n"; + } + continue; + } + $cmd .= '$' . strlen($item) . "\r\n$item\r\n"; + } + return "*$count\r\n$cmd"; + } + + /** @inheritdoc */ + public static function decode(string $buffer, ConnectionInterface $connection): mixed + { + $type = $buffer[0]; + switch ($type) { + case ':': + return [$type, (int)substr($buffer, 1)]; + case '-': + case '+': + return [$type, substr($buffer, 1, strlen($buffer) - 3)]; + case '$': + if (str_starts_with($buffer, '$-1')) { + return [$type, null]; + } + $pos = strpos($buffer, "\r\n"); + return [$type, substr($buffer, $pos + 2, (int)substr($buffer, 1, $pos))]; + case '*': + if (str_starts_with($buffer, '*-1')) { + return [$type, null]; + } + $pos = strpos($buffer, "\r\n"); + $value = []; + $count = (int)substr($buffer, 1, $pos - 1); + while ($count--) { + $next_pos = strpos($buffer, "\r\n", $pos + 2); + if (!$next_pos) { + return 0; + } + $sub_type = $buffer[$pos + 2]; + switch ($sub_type) { + case ':': + $value[] = (int)substr($buffer, $pos + 3, $next_pos - $pos - 3); + $pos = $next_pos; + break; + case '-': + case '+': + $value[] = substr($buffer, $pos + 3, $next_pos - $pos - 3); + $pos = $next_pos; + break; + case '$': + if ($pos + 2 === strpos($buffer, '$-1', $pos)) { + $pos = $next_pos; + $value[] = null; + break; + } + $length = (int)substr($buffer, $pos + 3, $next_pos - $pos - 3); + $value[] = substr($buffer, $next_pos + 2, $length); + $pos = $next_pos + $length + 2; + break; + default: + return ['!', "protocol error, got '$sub_type' as reply type byte. buffer:" . bin2hex($buffer) . " pos:$pos"]; + } + } + return [$type, $value]; + default: + return ['!', "protocol error, got '$type' as reply type byte. buffer:" . bin2hex($buffer)]; + } + } +} \ No newline at end of file