/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.ignite.internal.processors.rest.protocols.tcp.redis; import java.nio.ByteBuffer; import java.util.Collection; import java.util.Map; import org.apache.ignite.IgniteCheckedException; /** * Parser to decode/encode Redis protocol (RESP) requests. */ public class GridRedisProtocolParser { /** + prefix. */ private static final byte SIMPLE_STRING = 43; /** $ */ private static final byte BULK_STRING = 36; /** : */ private static final byte INTEGER = 58; /** * */ static final byte ARRAY = 42; /** - */ private static final byte ERROR = 45; /** Carriage return code. */ private static final byte CR = 13; /** Line feed code. */ private static final byte LF = 10; /** CRLF. */ private static final byte[] CRLF = new byte[] {13, 10}; /** Generic error prefix. */ private static final byte[] ERR_GENERIC = "ERR ".getBytes(); /** Prefix for errors on operations with the wrong type. */ private static final byte[] ERR_TYPE = "WRONGTYPE ".getBytes(); /** Prefix for errors on authentication. */ private static final byte[] ERR_AUTH = "NOAUTH ".getBytes(); /** Null bulk string for nil response. */ private static final byte[] NIL = "$-1\r\n".getBytes(); /** OK response. */ private static final byte[] OK = "OK".getBytes(); /** * Reads an array into {@link GridRedisMessage}. * * @param buf Buffer. * @return {@link GridRedisMessage}. * @throws IgniteCheckedException */ public static GridRedisMessage readArray(ByteBuffer buf) throws IgniteCheckedException { byte b = buf.get(); if (b != ARRAY) throw new IgniteCheckedException("Invalid request byte! " + b); int arrLen = elCnt(buf); GridRedisMessage msg = new GridRedisMessage(arrLen); for (int i = 0; i < arrLen; i++) msg.append(readBulkStr(buf)); return msg; } /** * Reads a bulk string. * * @param buf Buffer. * @return Bulk string. * @throws IgniteCheckedException */ public static String readBulkStr(ByteBuffer buf) throws IgniteCheckedException { byte b = buf.get(); if (b != BULK_STRING) throw new IgniteCheckedException("Invalid bulk string prefix! " + b); int len = elCnt(buf); byte[] bulkStr = new byte[len]; buf.get(bulkStr, 0, len); if (buf.get() != CR || buf.get() != LF) throw new IgniteCheckedException("Invalid request syntax!"); return new String(bulkStr); } /** * Counts elements in buffer. * * @param buf Buffer. * @return Count of elements. */ private static int elCnt(ByteBuffer buf) throws IgniteCheckedException { byte[] arrLen = new byte[9]; int idx = 0; byte b = buf.get(); while (b != CR) { arrLen[idx++] = b; b = buf.get(); } if (buf.get() != LF) throw new IgniteCheckedException("Invalid request syntax!"); return Integer.parseInt(new String(arrLen, 0, idx)); } /** * Converts a simple string data to a {@link ByteBuffer}. * * @param val String to be converted to a simple string. * @return Redis simple string. */ public static ByteBuffer toSimpleString(String val) { byte[] b = val.getBytes(); return toSimpleString(b); } /** * Creates a simple string data as a {@link ByteBuffer}. * * @param b Bytes for a simple string. * @return Redis simple string. */ public static ByteBuffer toSimpleString(byte[] b) { ByteBuffer buf = ByteBuffer.allocate(b.length + 3); buf.put(SIMPLE_STRING); buf.put(b); buf.put(CRLF); buf.flip(); return buf; } /** * @return Standard OK string. */ public static ByteBuffer oKString() { return toSimpleString(OK); } /** * Creates a generic error response. * * @param errMsg Error message. * @return Error response. */ public static ByteBuffer toGenericError(String errMsg) { return toError(errMsg, ERR_GENERIC); } /** * Creates an error response on operation against the wrong data type. * * @param errMsg Error message. * @return Error response. */ public static ByteBuffer toTypeError(String errMsg) { return toError(errMsg, ERR_TYPE); } /** * Creates an error response. * * @param errMsg Error message. * @param errPrefix Error prefix. * @return Error response. */ private static ByteBuffer toError(String errMsg, byte[] errPrefix) { byte[] b = errMsg.getBytes(); ByteBuffer buf = ByteBuffer.allocate(b.length + errPrefix.length + 3); buf.put(ERROR); buf.put(errPrefix); buf.put(b); buf.put(CRLF); buf.flip(); return buf; } /** * Converts an integer result to a RESP integer. * * @param integer Integer result. * @return REDIS integer. */ public static ByteBuffer toInteger(String integer) { byte[] b = integer.getBytes(); ByteBuffer buf = ByteBuffer.allocate(b.length + 3); buf.put(INTEGER); buf.put(b); buf.put(CRLF); buf.flip(); return buf; } /** * Converts an integer result to a RESP integer. * * @param integer Integer result. * @return REDIS integer. */ public static ByteBuffer toInteger(int integer) { return toInteger(String.valueOf(integer)); } /** * Creates Nil response. * * @return Nil response. */ public static ByteBuffer nil() { ByteBuffer buf = ByteBuffer.allocate(NIL.length); buf.put(NIL); buf.flip(); return buf; } /** * Converts a resultant object to a bulk string. * * @param val Object. * @return Bulk string. */ public static ByteBuffer toBulkString(Object val) { assert val != null; byte[] b = String.valueOf(val).getBytes(); byte[] l = String.valueOf(b.length).getBytes(); ByteBuffer buf = ByteBuffer.allocate(b.length + l.length + 5); buf.put(BULK_STRING); buf.put(l); buf.put(CRLF); buf.put(b); buf.put(CRLF); buf.flip(); return buf; } /** * Converts a resultant map response to an array. * * @param vals Map. * @return Array response. */ public static ByteBuffer toArray(Map<Object, Object> vals) { return toArray(vals.values()); } /** * Converts a resultant collection response to an array. * * @param vals Array elements. * @return Array response. */ public static ByteBuffer toArray(Collection<Object> vals) { assert vals != null; byte[] arrSize = String.valueOf(vals.size()).getBytes(); ByteBuffer buf = ByteBuffer.allocateDirect(1024 * 1024); buf.put(ARRAY); buf.put(arrSize); buf.put(CRLF); for (Object val : vals) buf.put(toBulkString(val)); buf.flip(); return buf; } }