// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved.
//
// This software, the RabbitMQ Java client library, is triple-licensed under the
// Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2
// ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see
// LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL,
// please see LICENSE-APACHE2.
//
// This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND,
// either express or implied. See the LICENSE file for specific language governing
// rights and limitations of this software.
//
// If you have any questions regarding licensing, please contact us at
// info@rabbitmq.com.
package com.rabbitmq.client.test.functional;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.Socket;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeoutException;
import com.rabbitmq.client.test.TestUtils;
import org.junit.Test;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Address;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.impl.AMQCommand;
import com.rabbitmq.client.impl.AMQConnection;
import com.rabbitmq.client.impl.Frame;
import com.rabbitmq.client.impl.FrameHandler;
import com.rabbitmq.client.impl.LongStringHelper;
import com.rabbitmq.client.impl.SocketFrameHandler;
import com.rabbitmq.client.test.BrokerTestCase;
public class FrameMax extends BrokerTestCase {
/* This value for FrameMax is larger than the minimum and less
* than what Rabbit suggests. */
final static int FRAME_MAX = 70000;
final static int REAL_FRAME_MAX = FRAME_MAX - 8;
public FrameMax() {
connectionFactory = new MyConnectionFactory();
connectionFactory.setRequestedFrameMax(FRAME_MAX);
}
@Test public void negotiationOk() {
assertEquals(FRAME_MAX, connection.getFrameMax());
}
/* Publish a message of size FRAME_MAX. The broker should split
* this into two frames before sending back. Frame content should
* be less or equal to frame-max - 8. */
@Test public void frameSizes()
throws IOException, InterruptedException
{
String queueName = channel.queueDeclare().getQueue();
/* This should result in at least 3 frames. */
int howMuch = 2*FRAME_MAX;
basicPublishVolatile(new byte[howMuch], queueName);
/* Receive everything that was sent out. */
while (howMuch > 0) {
try {
GetResponse response = channel.basicGet(queueName, false);
howMuch -= response.getBody().length;
} catch (Exception e) {
e.printStackTrace();
fail("Exception in basicGet loop: " + e);
}
}
}
/* server should reject frames larger than AMQP.FRAME_MIN_SIZE
* during connection negotiation */
@Test public void rejectLargeFramesDuringConnectionNegotiation()
throws IOException, TimeoutException {
ConnectionFactory cf = TestUtils.connectionFactory();
cf.getClientProperties().put("too_long", LongStringHelper.asLongString(new byte[AMQP.FRAME_MIN_SIZE]));
try {
cf.newConnection();
fail("Expected exception during connection negotiation");
} catch (IOException e) {
}
}
/* server should reject frames larger than the negotiated frame
* size */
@Test public void rejectExceedingFrameMax()
throws IOException, TimeoutException {
closeChannel();
closeConnection();
ConnectionFactory cf = new GenerousConnectionFactory();
connection = cf.newConnection();
openChannel();
basicPublishVolatile(new byte[connection.getFrameMax()], "void");
expectError(AMQP.FRAME_ERROR);
}
/* ConnectionFactory that uses MyFrameHandler rather than
* SocketFrameHandler. */
private static class MyConnectionFactory extends ConnectionFactory {
public MyConnectionFactory() {
super();
if(TestUtils.USE_NIO) {
useNio();
} else {
useBlockingIo();
}
}
protected FrameHandler createFrameHandler(Socket sock)
throws IOException
{
return new MyFrameHandler(sock);
}
}
/* FrameHandler with added frame-max error checking. */
private static class MyFrameHandler extends SocketFrameHandler {
public MyFrameHandler(Socket socket)
throws IOException
{
super(socket);
}
public Frame readFrame() throws IOException {
Frame f = super.readFrame();
int size = f.getPayload().length;
if (size > REAL_FRAME_MAX)
fail("Received frame of size " + size
+ ", which exceeds " + REAL_FRAME_MAX + ".");
//System.out.printf("Received a frame of size %d.\n", f.getPayload().length);
return f;
}
}
/*
AMQConnection with a frame_max that is one higher than what it
tells the server.
*/
private static class GenerousAMQConnection extends AMQConnection {
public GenerousAMQConnection(ConnectionFactory factory,
FrameHandler handler,
ExecutorService executor) {
super(factory.params(executor), handler);
}
@Override public int getFrameMax() {
// the RabbitMQ broker permits frames that are oversize by
// up to EMPTY_FRAME_SIZE octets
return super.getFrameMax() + AMQCommand.EMPTY_FRAME_SIZE + 1;
}
}
private static class GenerousConnectionFactory extends ConnectionFactory {
public GenerousConnectionFactory() {
super();
if(TestUtils.USE_NIO) {
useNio();
} else {
useBlockingIo();
}
}
@Override public Connection newConnection(ExecutorService executor, List<Address> addrs)
throws IOException, TimeoutException {
IOException lastException = null;
for (Address addr : addrs) {
try {
FrameHandler frameHandler = createFrameHandlerFactory().create(addr);
AMQConnection conn = new GenerousAMQConnection(this, frameHandler, executor);
conn.start();
return conn;
} catch (IOException e) {
lastException = e;
}
}
throw (lastException != null) ? lastException
: new IOException("failed to connect");
}
}
}