/* * 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; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.UUID; import java.util.concurrent.Callable; import org.apache.ignite.internal.client.marshaller.GridClientMarshaller; import org.apache.ignite.internal.client.marshaller.optimized.GridClientOptimizedMarshaller; import org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage; import org.apache.ignite.internal.util.nio.GridNioSession; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.processors.rest.client.message.GridClientCacheRequest.GridCacheOperation.CAS; import static org.apache.ignite.internal.processors.rest.protocols.tcp.GridMemcachedMessage.IGNITE_HANDSHAKE_FLAG; import static org.apache.ignite.internal.processors.rest.protocols.tcp.GridMemcachedMessage.IGNITE_REQ_FLAG; import static org.apache.ignite.internal.processors.rest.protocols.tcp.GridMemcachedMessage.MEMCACHE_REQ_FLAG; import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.MARSHALLER; /** * This class tests that parser confirms memcache extended specification. */ @SuppressWarnings("TypeMayBeWeakened") public class TcpRestParserSelfTest extends GridCommonAbstractTest { /** Marshaller. */ private GridClientMarshaller marshaller = new GridClientOptimizedMarshaller(); /** Extras value. */ public static final byte[] EXTRAS = new byte[]{ (byte)0xDE, 0x00, (byte)0xBE, 0x00, //Flags, string encoding. 0x00, 0x00, 0x00, 0x00 // Expiration value. }; /** * @throws Exception If failed. */ public void testSimplePacketParsing() throws Exception { GridNioSession ses = new MockNioSession(); GridTcpRestParser parser = new GridTcpRestParser(false); byte hdr = MEMCACHE_REQ_FLAG; byte[] opCodes = {0x01, 0x02, 0x03}; byte[] opaque = new byte[] {0x01, 0x02, 0x03, (byte)0xFF}; String key = "key"; String val = "value"; for (byte opCode : opCodes) { ByteBuffer raw = rawPacket(hdr, opCode, opaque, key.getBytes(), val.getBytes(), EXTRAS); GridClientMessage msg = parser.decode(ses, raw); assertTrue(msg instanceof GridMemcachedMessage); GridMemcachedMessage packet = (GridMemcachedMessage)msg; assertEquals("Parser leaved unparsed bytes", 0, raw.remaining()); assertEquals("Invalid opcode", opCode, packet.operationCode()); assertEquals("Invalid key", key, packet.key()); assertEquals("Invalid value", val, packet.value()); } } /** * @throws Exception If failed. */ public void testIncorrectPackets() throws Exception { final GridNioSession ses = new MockNioSession(); final GridTcpRestParser parser = new GridTcpRestParser(false); final byte[] opaque = new byte[] {0x01, 0x02, 0x03, (byte)0xFF}; final String key = "key"; final String val = "value"; GridTestUtils.assertThrows(log(), new Callable<Object>() { @Nullable @Override public Object call() throws Exception { parser.decode(ses, rawPacket((byte)0x01, (byte)0x01, opaque, key.getBytes(), val.getBytes(), EXTRAS)); return null; } }, IOException.class, null); GridTestUtils.assertThrows(log(), new Callable<Object>() { @Nullable @Override public Object call() throws Exception { parser.decode(ses, rawPacket(MEMCACHE_REQ_FLAG, (byte)0x01, opaque, key.getBytes(), val.getBytes(), null)); return null; } }, IOException.class, null); GridTestUtils.assertThrows(log(), new Callable<Object>() { @Nullable @Override public Object call() throws Exception { ByteBuffer fake = ByteBuffer.allocate(21); fake.put(IGNITE_REQ_FLAG); fake.put(U.intToBytes(-5)); fake.put(U.longToBytes(0)); fake.put(U.longToBytes(0)); fake.flip(); parser.decode(ses, fake); return null; } }, IOException.class, null); } /** * @throws Exception If failed. */ public void testCustomMessages() throws Exception { GridClientCacheRequest req = new GridClientCacheRequest(CAS); req.key("key"); req.value(1); req.value2(2); req.clientId(UUID.randomUUID()); ByteBuffer raw = clientRequestPacket(req); GridNioSession ses = new MockNioSession(); ses.addMeta(MARSHALLER.ordinal(), new GridClientOptimizedMarshaller()); GridTcpRestParser parser = new GridTcpRestParser(false); GridClientMessage msg = parser.decode(ses, raw); assertNotNull(msg); assertEquals("Parser leaved unparsed bytes", 0, raw.remaining()); assertTrue(msg instanceof GridClientCacheRequest); GridClientCacheRequest res = (GridClientCacheRequest) msg; assertEquals("Invalid operation", req.operation(), res.operation()); assertEquals("Invalid clientId", req.clientId(), res.clientId()); assertEquals("Invalid key", req.key(), res.key()); assertEquals("Invalid value 1", req.value(), res.value()); assertEquals("Invalid value 2", req.value2(), res.value2()); } /** * @throws Exception If failed. */ public void testMixedParsing() throws Exception { GridNioSession ses1 = new MockNioSession(); GridNioSession ses2 = new MockNioSession(); ses1.addMeta(MARSHALLER.ordinal(), new GridClientOptimizedMarshaller()); ses2.addMeta(MARSHALLER.ordinal(), new GridClientOptimizedMarshaller()); GridTcpRestParser parser = new GridTcpRestParser(false); GridClientCacheRequest req = new GridClientCacheRequest(CAS); req.key("key"); String val = "value"; req.value(val); req.value2(val); req.clientId(UUID.randomUUID()); byte[] opaque = new byte[]{0x01, 0x02, 0x03, (byte)0xFF}; String key = "key"; ByteBuffer raw1 = rawPacket(MEMCACHE_REQ_FLAG, (byte)0x01, opaque, key.getBytes(), val.getBytes(), EXTRAS); ByteBuffer raw2 = clientRequestPacket(req); raw1.mark(); raw2.mark(); int splits = Math.min(raw1.remaining(), raw2.remaining()); for (int i = 1; i < splits; i++) { ByteBuffer[] packet1 = split(raw1, i); ByteBuffer[] packet2 = split(raw2, i); GridClientMessage msg = parser.decode(ses1, packet1[0]); assertNull(msg); msg = parser.decode(ses2, packet2[0]); assertNull(msg); msg = parser.decode(ses1, packet1[1]); assertTrue(msg instanceof GridMemcachedMessage); assertEquals(key, ((GridMemcachedMessage)msg).key()); assertEquals(val, ((GridMemcachedMessage)msg).value()); msg = parser.decode(ses2, packet2[1]); assertTrue(msg instanceof GridClientCacheRequest); assertEquals(val, ((GridClientCacheRequest)msg).value()); assertEquals(val, ((GridClientCacheRequest)msg).value2()); raw1.reset(); raw2.reset(); } } /** * @throws Exception If failed. */ public void testParseContinuousSplit() throws Exception { ByteBuffer tmp = ByteBuffer.allocate(10 * 1024); GridClientCacheRequest req = new GridClientCacheRequest(CAS); req.key("key"); req.value(1); req.value2(2); req.clientId(UUID.randomUUID()); for (int i = 0; i < 5; i++) tmp.put(clientRequestPacket(req)); tmp.flip(); for (int splitPos = 0; splitPos < tmp.remaining(); splitPos++) { ByteBuffer[] split = split(tmp, splitPos); tmp.flip(); GridNioSession ses = new MockNioSession(); ses.addMeta(MARSHALLER.ordinal(), new GridClientOptimizedMarshaller()); GridTcpRestParser parser = new GridTcpRestParser(false); Collection<GridClientCacheRequest> lst = new ArrayList<>(5); for (ByteBuffer buf : split) { GridClientCacheRequest r; while (buf.hasRemaining() && (r = (GridClientCacheRequest)parser.decode(ses, buf)) != null) lst.add(r); assertTrue("Parser has left unparsed bytes.", buf.remaining() == 0); } assertEquals(5, lst.size()); for (GridClientCacheRequest res : lst) { assertEquals("Invalid operation", req.operation(), res.operation()); assertEquals("Invalid clientId", req.clientId(), res.clientId()); assertEquals("Invalid key", req.key(), res.key()); assertEquals("Invalid value 1", req.value(), res.value()); assertEquals("Invalid value 2", req.value2(), res.value2()); } } } /** * Tests correct parsing of client handshake packets. * * @throws Exception If failed. */ public void testParseClientHandshake() throws Exception { for (int splitPos = 1; splitPos < 5; splitPos++) { log.info("Checking split position: " + splitPos); ByteBuffer tmp = clientHandshakePacket(); ByteBuffer[] split = split(tmp, splitPos); GridNioSession ses = new MockNioSession(); ses.addMeta(MARSHALLER.ordinal(), new GridClientOptimizedMarshaller()); GridTcpRestParser parser = new GridTcpRestParser(false); Collection<GridClientMessage> lst = new ArrayList<>(1); for (ByteBuffer buf : split) { GridClientMessage r; while (buf.hasRemaining() && (r = parser.decode(ses, buf)) != null) lst.add(r); assertTrue("Parser has left unparsed bytes.", buf.remaining() == 0); } assertEquals(1, lst.size()); GridClientHandshakeRequest req = (GridClientHandshakeRequest)F.first(lst); assertNotNull(req); assertEquals(U.bytesToShort(new byte[]{5, 0}, 0), req.version()); } } /** * Splits given byte buffer into two byte buffers. * * @param original Original byte buffer. * @param pos Position at which buffer should be split. * @return Array of byte buffers. */ private ByteBuffer[] split(ByteBuffer original, int pos) { byte[] data = new byte[pos]; original.get(data); ByteBuffer[] res = new ByteBuffer[2]; res[0] = ByteBuffer.wrap(data); data = new byte[original.remaining()]; original.get(data); res[1] = ByteBuffer.wrap(data); return res; } /** * Assembles Ignite client packet. * * @param msg Message to serialize. * @return Raw message bytes. * @throws IOException If serialization failed. */ private ByteBuffer clientRequestPacket(GridClientMessage msg) throws IOException { ByteBuffer res = marshaller.marshal(msg, 45); ByteBuffer slice = res.slice(); slice.put(IGNITE_REQ_FLAG); slice.putInt(res.remaining() - 5); slice.putLong(msg.requestId()); slice.put(U.uuidToBytes(msg.clientId())); slice.put(U.uuidToBytes(msg.destinationId())); return res; } /** * Assembles Ignite client handshake packet. * * @return Raw message bytes. */ private ByteBuffer clientHandshakePacket() { ByteBuffer res = ByteBuffer.allocate(6); res.put(new byte[] { IGNITE_HANDSHAKE_FLAG, 5, 0, 0, 0, 0 }); res.flip(); return res; } /** * Assembles raw packet without any logical checks. * * @param magic Header for packet. * @param opCode Operation code. * @param opaque Opaque value. * @param key Key data. * @param val Value data. * @param extras Extras data. * @return Byte buffer containing assembled packet. */ private ByteBuffer rawPacket(byte magic, byte opCode, byte[] opaque, @Nullable byte[] key, @Nullable byte[] val, @Nullable byte[] extras) { // 1k should be enough. ByteBuffer res = ByteBuffer.allocate(1024); res.put(magic); res.put(opCode); int keyLen = key == null ? 0 : key.length; int extrasLen = extras == null ? 0 : extras.length; int valLen = val == null ? 0 : val.length; res.putShort((short)keyLen); res.put((byte)extrasLen); // Data type is always 0. res.put((byte)0); // Reserved. res.putShort((short)0); // Total body. res.putInt(keyLen + extrasLen + valLen); // Opaque. res.put(opaque); // CAS res.putLong(0); if (extrasLen > 0) res.put(extras); if (keyLen > 0) res.put(key); if (valLen > 0) res.put(val); res.flip(); return res; } }