/** * 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.activemq.artemis.message; import java.util.LinkedList; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import org.apache.activemq.artemis.api.core.ActiveMQBuffer; import org.apache.activemq.artemis.api.core.ActiveMQBuffers; import org.apache.activemq.artemis.api.core.Message; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.client.impl.ClientMessageImpl; import org.apache.activemq.artemis.core.message.impl.CoreMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionReceiveMessage; import org.apache.activemq.artemis.core.protocol.core.impl.wireformat.SessionSendMessage; import org.apache.activemq.artemis.reader.TextMessageUtil; import org.apache.activemq.artemis.utils.Base64; import org.apache.activemq.artemis.utils.ByteUtil; import org.apache.activemq.artemis.utils.UUID; import org.junit.Assert; import org.junit.Before; import org.junit.Ignore; import org.junit.Test; public class CoreMessageTest { public static final SimpleString ADDRESS = new SimpleString("this.local.address"); public static final byte MESSAGE_TYPE = Message.TEXT_TYPE; public static final boolean DURABLE = true; public static final long EXPIRATION = 123L; public static final long TIMESTAMP = 321L; public static final byte PRIORITY = (byte) 3; public static final String TEXT = "hi"; public static final String BIGGER_TEXT = "AAAAAAAAAAAAAAAAAAAAAAAAA ASDF ASDF ASF ASD ASF ASDF ASDF ASDF ASF ADSF ASDF"; public static final String SMALLER_TEXT = "H"; public static final UUID uuid = new UUID(UUID.TYPE_TIME_BASED, new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}); public static final SimpleString PROP1_NAME = new SimpleString("t1"); public static final SimpleString PROP1_VALUE = new SimpleString("value-t1"); /** * This encode was generated by {@link #generate()}. * Run it manually with a right-click on the IDE to eventually update it * */ // body = "hi"; private final String STRING_ENCODE = "AAAAFgEAAAAEaABpAAAAAAAAAAAAAQAAACR0AGgAaQBzAC4AbABvAGMAYQBsAC4AYQBkAGQAcgBlAHMAcwAAAwEAAAAAAAAAewAAAAAAAAFBAwEAAAABAAAABHQAMQAKAAAAEHYAYQBsAHUAZQAtAHQAMQA="; private ByteBuf BYTE_ENCODE; @Before public void before() { BYTE_ENCODE = Unpooled.wrappedBuffer(Base64.decode(STRING_ENCODE, Base64.DONT_BREAK_LINES | Base64.URL_SAFE)); // some extra caution here, nothing else, to make sure we would get the same encoding back Assert.assertEquals(STRING_ENCODE, encodeString(BYTE_ENCODE.array())); BYTE_ENCODE.readerIndex(0).writerIndex(BYTE_ENCODE.capacity()); } /** The message is received, then sent to the other side untouched */ @Test public void testPassThrough() { CoreMessage decodedMessage = decodeMessage(); Assert.assertEquals(TEXT, TextMessageUtil.readBodyText(decodedMessage.getReadOnlyBodyBuffer()).toString()); } /** The message is received, then sent to the other side untouched */ @Test public void sendThroughPackets() { CoreMessage decodedMessage = decodeMessage(); int encodeSize = decodedMessage.getEncodeSize(); Assert.assertEquals(BYTE_ENCODE.capacity(), encodeSize); SessionSendMessage sendMessage = new SessionSendMessage(decodedMessage, true, null); sendMessage.setChannelID(777); ActiveMQBuffer buffer = sendMessage.encode(null); byte[] byteArray = buffer.byteBuf().array(); System.out.println("Sending " + ByteUtil.bytesToHex(buffer.toByteBuffer().array(), 1) + ", bytes = " + byteArray.length); buffer.readerIndex(5); SessionSendMessage sendMessageReceivedSent = new SessionSendMessage(new CoreMessage()); sendMessageReceivedSent.decode(buffer); Assert.assertEquals(encodeSize, sendMessageReceivedSent.getMessage().getEncodeSize()); Assert.assertTrue(sendMessageReceivedSent.isRequiresResponse()); Assert.assertEquals(TEXT, TextMessageUtil.readBodyText(sendMessageReceivedSent.getMessage().getReadOnlyBodyBuffer()).toString()); } /** The message is received, then sent to the other side untouched */ @Test public void sendThroughPacketsClient() { CoreMessage decodedMessage = decodeMessage(); int encodeSize = decodedMessage.getEncodeSize(); Assert.assertEquals(BYTE_ENCODE.capacity(), encodeSize); SessionReceiveMessage sendMessage = new SessionReceiveMessage(33, decodedMessage, 7); sendMessage.setChannelID(777); ActiveMQBuffer buffer = sendMessage.encode(null); buffer.readerIndex(5); SessionReceiveMessage sendMessageReceivedSent = new SessionReceiveMessage(new CoreMessage()); sendMessageReceivedSent.decode(buffer); Assert.assertEquals(33, sendMessageReceivedSent.getConsumerID()); Assert.assertEquals(7, sendMessageReceivedSent.getDeliveryCount()); Assert.assertEquals(encodeSize, sendMessageReceivedSent.getMessage().getEncodeSize()); Assert.assertEquals(TEXT, TextMessageUtil.readBodyText(sendMessageReceivedSent.getMessage().getReadOnlyBodyBuffer()).toString()); } private CoreMessage decodeMessage() { ByteBuf newBuffer = Unpooled.buffer(BYTE_ENCODE.capacity()); newBuffer.writeBytes(BYTE_ENCODE, 0, BYTE_ENCODE.writerIndex()); CoreMessage coreMessage = internalDecode(newBuffer); int encodeSize = coreMessage.getEncodeSize(); Assert.assertEquals(newBuffer.capacity(), encodeSize); Assert.assertEquals(ADDRESS, coreMessage.getAddressSimpleString()); Assert.assertEquals(PROP1_VALUE.toString(), coreMessage.getStringProperty(PROP1_NAME)); ByteBuf destinedBuffer = Unpooled.buffer(BYTE_ENCODE.array().length); coreMessage.sendBuffer(destinedBuffer, 0); byte[] destinedArray = destinedBuffer.array(); byte[] sourceArray = BYTE_ENCODE.array(); CoreMessage newDecoded = internalDecode(Unpooled.wrappedBuffer(destinedArray)); Assert.assertEquals(encodeSize, newDecoded.getEncodeSize()); Assert.assertArrayEquals(sourceArray, destinedArray); return coreMessage; } private CoreMessage internalDecode(ByteBuf bufferOrigin) { CoreMessage coreMessage = new CoreMessage(); // System.out.println("Bytes from test " + ByteUtil.bytesToHex(bufferOrigin.array(), 1)); coreMessage.receiveBuffer(bufferOrigin); return coreMessage; } /** The message is received, then sent to the other side untouched */ @Test public void testChangeBodyStringSameSize() { testChangeBodyString(TEXT.toUpperCase()); } @Test public void testChangeBodyBiggerString() { testChangeBodyString(BIGGER_TEXT); } @Test public void testGenerateEmpty() { CoreMessage empty = new CoreMessage().initBuffer(100); ByteBuf buffer = Unpooled.buffer(200); empty.sendBuffer(buffer, 0); CoreMessage empty2 = new CoreMessage(); empty2.receiveBuffer(buffer); try { empty2.getBodyBuffer().readLong(); Assert.fail("should throw exception"); } catch (Exception expected) { } } @Test public void testSaveReceiveLimitedBytes() { CoreMessage empty = new CoreMessage().initBuffer(100); System.out.println("R " + empty.getBodyBuffer().readerIndex() + " W " + empty.getBodyBuffer().writerIndex()); empty.getBodyBuffer().writeByte((byte)7); System.out.println("R " + empty.getBodyBuffer().readerIndex() + " W " + empty.getBodyBuffer().writerIndex()); ByteBuf buffer = Unpooled.buffer(200); empty.sendBuffer(buffer, 0); CoreMessage empty2 = new CoreMessage(); empty2.receiveBuffer(buffer); Assert.assertEquals((byte)7, empty2.getBodyBuffer().readByte()); System.out.println("Readable :: " + empty2.getBodyBuffer().readerIndex() + " writer :" + empty2.getBodyBuffer().writerIndex()); try { empty2.getBodyBuffer().readByte(); Assert.fail("should throw exception"); } catch (Exception expected) { } } @Test public void testChangeBodySmallerString() { testChangeBodyString(SMALLER_TEXT); } public void testChangeBodyString(String newString) { CoreMessage coreMessage = decodeMessage(); coreMessage.putStringProperty("newProperty", "newValue"); ActiveMQBuffer legacyBuffer = coreMessage.getBodyBuffer(); legacyBuffer.resetWriterIndex(); legacyBuffer.clear(); TextMessageUtil.writeBodyText(legacyBuffer, SimpleString.toSimpleString(newString)); ByteBuf newbuffer = Unpooled.buffer(150000); coreMessage.sendBuffer(newbuffer, 0); newbuffer.readerIndex(0); CoreMessage newCoreMessage = new CoreMessage(); newCoreMessage.receiveBuffer(newbuffer); SimpleString newText = TextMessageUtil.readBodyText(newCoreMessage.getReadOnlyBodyBuffer()); Assert.assertEquals(newString, newText.toString()); // coreMessage.putStringProperty() } @Test public void testPassThroughMultipleThreads() throws Throwable { CoreMessage coreMessage = new CoreMessage(); coreMessage.receiveBuffer(BYTE_ENCODE); LinkedList<Throwable> errors = new LinkedList<>(); Thread[] threads = new Thread[50]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { try { for (int j = 0; j < 50; j++) { Assert.assertEquals(ADDRESS, coreMessage.getAddressSimpleString()); Assert.assertEquals(PROP1_VALUE.toString(), coreMessage.getStringProperty(PROP1_NAME)); ByteBuf destinedBuffer = Unpooled.buffer(BYTE_ENCODE.array().length); coreMessage.sendBuffer(destinedBuffer, 0); byte[] destinedArray = destinedBuffer.array(); byte[] sourceArray = BYTE_ENCODE.array(); Assert.assertArrayEquals(sourceArray, destinedArray); Assert.assertEquals(TEXT, TextMessageUtil.readBodyText(coreMessage.getReadOnlyBodyBuffer()).toString()); } } catch (Throwable e) { e.printStackTrace(); errors.add(e); } }); } for (Thread t : threads) { t.start(); } for (Thread t : threads) { t.join(); } for (Throwable e: errors) { throw e; } } // This is to compare the original encoding with the current version @Test public void compareOriginal() throws Exception { String generated = generate(TEXT); Assert.assertEquals(STRING_ENCODE, generated); for (int i = 0; i < generated.length(); i++) { Assert.assertEquals("Chart at " + i + " was " + generated.charAt(i) + " instead of " + STRING_ENCODE.charAt(i), generated.charAt(i), STRING_ENCODE.charAt(i)); } } /** Use this method to update the encode for the known message */ @Ignore @Test public void generate() throws Exception { printVariable(TEXT, generate(TEXT)); printVariable(SMALLER_TEXT, generate(SMALLER_TEXT)); printVariable(BIGGER_TEXT, generate(BIGGER_TEXT)); } private void printVariable(String body, String encode) { System.out.println("// body = \"" + body + "\";"); System.out.println("private final String STRING_ENCODE = \"" + encode + "\";"); } public String generate(String body) throws Exception { ClientMessageImpl message = new ClientMessageImpl(MESSAGE_TYPE, DURABLE, EXPIRATION, TIMESTAMP, PRIORITY, 10 * 1024); TextMessageUtil.writeBodyText(message.getBodyBuffer(), SimpleString.toSimpleString(body)); message.setAddress(ADDRESS); message.setUserID(uuid); message.getProperties().putSimpleStringProperty(PROP1_NAME, PROP1_VALUE); ActiveMQBuffer buffer = ActiveMQBuffers.dynamicBuffer(10 * 1024); message.sendBuffer(buffer.byteBuf(), 0); byte[] bytes = new byte[buffer.byteBuf().writerIndex()]; buffer.byteBuf().readBytes(bytes); return encodeString(bytes); // replace the code } private String encodeString(byte[] bytes) { return Base64.encodeBytes(bytes, 0, bytes.length, Base64.DONT_BREAK_LINES | Base64.URL_SAFE); } }