package org.infinispan.server.memcached; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.OutputStream; import java.math.BigInteger; import java.nio.charset.Charset; import java.util.Collections; import java.util.List; import java.util.stream.Collectors; import java.util.stream.Stream; import org.infinispan.util.KeyValuePair; import io.netty.buffer.ByteBuf; /** * Memcached text protocol utilities. * * @author Galder ZamarreƱo * @since 4.1 */ public class TextProtocolUtil { private TextProtocolUtil() { } // todo: refactor name once old code has been removed? public static final String CRLF = "\r\n"; public static final byte[] CRLFBytes = "\r\n".getBytes(); public static final byte[] END = "END\r\n".getBytes(); public static final int END_SIZE = END.length; public static final byte[] DELETED = "DELETED\r\n".getBytes(); public static final byte[] NOT_FOUND = "NOT_FOUND\r\n".getBytes(); public static final byte[] EXISTS = "EXISTS\r\n".getBytes(); public static final byte[] STORED = "STORED\r\n".getBytes(); public static final byte[] NOT_STORED = "NOT_STORED\r\n".getBytes(); public static final byte[] OK = "OK\r\n".getBytes(); public static final byte[] ERROR = "ERROR\r\n".getBytes(); public static final String CLIENT_ERROR_BAD_FORMAT = "CLIENT_ERROR bad command line format: "; public static final String SERVER_ERROR = "SERVER_ERROR "; public static final byte[] VALUE = "VALUE ".getBytes(); public static final int VALUE_SIZE = VALUE.length; public static final byte[] ZERO = "0".getBytes(); public static final int SP = 32; public static final int CR = 13; public static final int LF = 10; public static final BigInteger MAX_UNSIGNED_LONG = new BigInteger("18446744073709551615"); public static final BigInteger MIN_UNSIGNED = new BigInteger("0"); public static final Charset CHARSET = Charset.forName("UTF-8"); /** * In the particular case of Memcached, the end of operation/command * is signaled by "\r\n" characters. So, if end of operation is * found, this method would return true. On the contrary, if space was * found instead of end of operation character, then it'd return the element and false. */ static boolean readElement(ByteBuf buffer, OutputStream byteBuffer) throws IOException { byte next = buffer.readByte(); if (next == SP) { // Space return false; } else if (next == CR) { // CR next = buffer.readByte(); if (next == LF) { // LF return true; } else { byteBuffer.write(next); return readElement(buffer, byteBuffer); } } else { byteBuffer.write(next); return readElement(buffer, byteBuffer); } } static String extractString(ByteArrayOutputStream byteBuffer) { String string = new String(byteBuffer.toByteArray(), CHARSET); byteBuffer.reset(); return string; } static KeyValuePair<String, Boolean> readElement(ByteBuf buffer, StringBuilder sb) { byte next = buffer.readByte(); if (next == SP) { // Space return new KeyValuePair<>(sb.toString().trim(), false); } else if (next == CR) { // CR next = buffer.readByte(); if (next == LF) { // LF return new KeyValuePair<>(sb.toString().trim(), true); } else { sb.append(next); return readElement(buffer, sb); } } else { sb.append(next); return readElement(buffer, sb); } } static String readDiscardedLine(ByteBuf buffer) { if (readableBytes(buffer) > 0) return readDiscardedLine(buffer, new StringBuilder()); else return ""; } private static int readableBytes(ByteBuf buffer) { return buffer.writerIndex() - buffer.readerIndex(); } private static String readDiscardedLine(ByteBuf buffer, StringBuilder sb) { byte next = buffer.readByte(); if (next == CR) { // CR next = buffer.readByte(); if (next == LF) { // LF return sb.toString().trim(); } else { sb.append((char) next); return readDiscardedLine(buffer, sb); } } else if (next == LF) { //LF return sb.toString().trim(); } else { sb.append((char) next); return readDiscardedLine(buffer, sb); } } static void skipLine(ByteBuf buffer) { if (readableBytes(buffer) > 0) { byte next = buffer.readByte(); if (next == CR) { // CR next = buffer.readByte(); if (next == LF) { // LF return; } else { skipLine(buffer); } } else if (next == LF) { //LF return; } else { skipLine(buffer); } } } static byte[] concat(byte[] a, byte[] b) { byte[] data = new byte[a.length + b.length]; System.arraycopy(a, 0, data, 0, a.length); System.arraycopy(b, 0, data, a.length, b.length); return data; } static List<String> readSplitLine(ByteBuf buffer) { if (readableBytes(buffer) > 0) return readSplitLine(buffer, Stream.builder(), buffer.readerIndex(), 0).collect(Collectors.toList()); else return Collections.emptyList(); } private static Stream<String> readSplitLine(ByteBuf buffer, Stream.Builder<String> builder, int start, int length) { byte next = buffer.readByte(); if (next == CR) { // CR next = buffer.readByte(); if (next == LF) { // LF byte[] bytes = new byte[length]; buffer.getBytes(start, bytes); builder.add(new String(bytes, CHARSET)); return builder.build(); } else { return readSplitLine(buffer, builder, start, length + 1); } } else if (next == LF) { // LF byte[] bytes = new byte[length]; buffer.getBytes(start, bytes); builder.add(new String(bytes, CHARSET)); return builder.build(); } else if (next == SP) { byte[] bytes = new byte[length]; buffer.getBytes(start, bytes); builder.add(new String(bytes, CHARSET)); return readSplitLine(buffer, builder, start + length + 1, 0); } else { return readSplitLine(buffer, builder, start, length + 1); } } }