/*
* Copyright 1999-2011 Alibaba Group.
*
* Licensed 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 com.alibaba.dubbo.remoting.codec;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import org.junit.Before;
import org.junit.Test;
import com.alibaba.dubbo.common.Constants;
import com.alibaba.dubbo.common.extension.ExtensionLoader;
import com.alibaba.dubbo.common.io.Bytes;
import com.alibaba.dubbo.common.io.UnsafeByteArrayOutputStream;
import com.alibaba.dubbo.common.serialize.ObjectOutput;
import com.alibaba.dubbo.common.serialize.Serialization;
import com.alibaba.dubbo.remoting.Channel;
import com.alibaba.dubbo.remoting.buffer.ChannelBuffer;
import com.alibaba.dubbo.remoting.buffer.ChannelBuffers;
import com.alibaba.dubbo.remoting.exchange.Request;
import com.alibaba.dubbo.remoting.exchange.Response;
import com.alibaba.dubbo.remoting.exchange.codec.ExchangeCodec;
import com.alibaba.dubbo.remoting.telnet.codec.TelnetCodec;
import junit.framework.Assert;
import static org.junit.Assert.fail;
/**
* @author chao.liuc
* byte 16
* 0-1 magic code
* 2 flag
* 8 - 1-request/0-response
* 7 - two way
* 6 - heartbeat
* 1-5 serialization id
* 3 status
* 20 ok
* 90 error?
* 4-11 id (long)
* 12 -15 datalength
*
*/
public class ExchangeCodecTest extends TelnetCodecTest{
// magic header.
private static final short MAGIC = (short) 0xdabb;
private static final byte MAGIC_HIGH = (byte) Bytes.short2bytes(MAGIC)[0];
private static final byte MAGIC_LOW = (byte) Bytes.short2bytes(MAGIC)[1];
Serialization serialization = getSerialization(Constants.DEFAULT_REMOTING_SERIALIZATION);
private Object decode(byte[] request) throws IOException{
ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(request);
AbstractMockChannel channel = getServerSideChannel(url);
//decode
Object obj = codec.decode(channel, buffer);
return obj;
}
private byte[] getRequestBytes(Object obj, byte[] header) throws IOException{
// encode request data.
UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(1024);
ObjectOutput out = serialization.serialize(url, bos);
out.writeObject(obj);
out.flushBuffer();
bos.flush();
bos.close();
byte[] data = bos.toByteArray();
byte[] len = Bytes.int2bytes(data.length);
System.arraycopy(len, 0, header, 12, 4);
byte[] request = join(header, data);
return request;
}
private byte[] assemblyDataProtocol(byte[] header){
Person request = new Person();
byte[] newbuf = join(header, objectToByte(request));
return newbuf;
}
private static Serialization getSerialization(String name) {
Serialization serialization = ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(name);
return serialization;
}
//===================================================================================
@Before
public void setUp() throws Exception {
codec = new ExchangeCodec();
}
@Test
public void test_Decode_Error_MagicNum() throws IOException{
HashMap<byte[] , Object> inputBytes = new HashMap<byte[] , Object>();
inputBytes.put( new byte[] { 0 }, TelnetCodec.DecodeResult.NEED_MORE_INPUT );
inputBytes.put( new byte[] { MAGIC_HIGH, 0 }, TelnetCodec.DecodeResult.NEED_MORE_INPUT );
inputBytes.put( new byte[] { 0 , MAGIC_LOW }, TelnetCodec.DecodeResult.NEED_MORE_INPUT );
for (byte[] input : inputBytes.keySet()){
testDecode_assertEquals(assemblyDataProtocol(input) ,inputBytes.get(input));
}
}
@Test
public void test_Decode_Error_Length() throws IOException{
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 0x20, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Channel channel = getServerSideChannel(url);
byte[] baddata = new byte[]{1,2};
ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(join(request, baddata));
Response obj = (Response)codec.decode(channel, buffer);
Assert.assertEquals(person, obj.getResult());
//only decode necessary bytes
Assert.assertEquals(request.length, buffer.readerIndex());
}
@Test
public void test_Decode_Error_Response_Object() throws IOException{
//00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 0x20, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
//bad object
byte[] badbytes = new byte[]{-1,-2,-3,-4,-3,-4,-3,-4,-3,-4,-3,-4};
System.arraycopy(badbytes, 0, request, 21, badbytes.length);
Response obj = (Response)decode(request);
Assert.assertEquals(90, obj.getStatus());
}
@Test
public void test_Decode_Check_Payload() throws IOException {
byte[] header = new byte[]{MAGIC_HIGH, MAGIC_LOW, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
byte[] request = assemblyDataProtocol(header);
try {
testDecode_assertEquals(request, TelnetCodec.DecodeResult.NEED_MORE_INPUT);
fail();
} catch (IOException expected) {
Assert.assertTrue(expected.getMessage().startsWith("Data length too large: " + Bytes.bytes2int(new byte[]{1, 1, 1, 1})));
}
}
@Test
public void test_Decode_Header_Need_Readmore() throws IOException{
byte[] header = new byte[] { MAGIC_HIGH , MAGIC_LOW , 0 ,0 ,0 ,0 ,0 , 0 ,0 ,0 ,0 };
testDecode_assertEquals(header, TelnetCodec.DecodeResult.NEED_MORE_INPUT);
}
@Test
public void test_Decode_Body_Need_Readmore() throws IOException{
byte[] header = new byte[] { MAGIC_HIGH , MAGIC_LOW , 0 ,0 ,0 ,0 ,0 , 0 ,0 ,0 ,0, 0, 0, 0 , 1 ,1, 'a', 'a' };
testDecode_assertEquals(header, TelnetCodec.DecodeResult.NEED_MORE_INPUT);
}
@Test
public void test_Decode_MigicCodec_Contain_ExchangeHeader() throws IOException{
//
byte[] header = new byte[] { 0, 0, MAGIC_HIGH , MAGIC_LOW , 0 ,0 ,0 ,0 ,0 , 0 ,0 ,0 ,0 };
Channel channel = getServerSideChannel(url);
ChannelBuffer buffer = ChannelBuffers.wrappedBuffer(header);
Object obj = codec.decode(channel, buffer);
Assert.assertEquals(TelnetCodec.DecodeResult.NEED_MORE_INPUT, obj);
//如果telnet数据与request数据在同一个数据包中,不能因为telnet没有结尾字符而影响其他数据的接收.
Assert.assertEquals(2, buffer.readerIndex());
}
@Test
public void test_Decode_Return_Response_Person() throws IOException{
//00000010-response/oneway/hearbeat=false/hessian |20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 2, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Response obj = (Response)decode(request);
Assert.assertEquals(20, obj.getStatus());
Assert.assertEquals(person, obj.getResult());
System.out.println(obj);
}
@Test //status输入有问题,序列化时读取信息出错.
public void test_Decode_Return_Response_Error() throws IOException{
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 2, 90, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
String errorString = "encode request data error ";
byte[] request = getRequestBytes(errorString, header);
Response obj = (Response)decode(request);
Assert.assertEquals(90, obj.getStatus());
Assert.assertEquals(errorString, obj.getErrorMessage());
}
@Test
public void test_Decode_Return_Request_Event_Object() throws IOException{
//|10011111|20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, (byte) 0xff, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Request obj = (Request)decode(request);
Assert.assertEquals(person, obj.getData());
Assert.assertEquals(true, obj.isTwoWay());
Assert.assertEquals(true, obj.isEvent());
Assert.assertEquals("2.0.0", obj.getVersion());
System.out.println(obj);
}
@Test
public void test_Decode_Return_Request_Event_String() throws IOException{
//|10011111|20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, (byte) 0xff, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
String event = Request.READONLY_EVENT;
byte[] request = getRequestBytes(event, header);
Request obj = (Request)decode(request);
Assert.assertEquals(event, obj.getData());
Assert.assertEquals(true, obj.isTwoWay());
Assert.assertEquals(true, obj.isEvent());
Assert.assertEquals("2.0.0", obj.getVersion());
System.out.println(obj);
}
@Test
public void test_Decode_Return_Request_Heartbeat_Object() throws IOException{
//|10011111|20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, (byte) 0xff, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
byte[] request = getRequestBytes(null, header);
Request obj = (Request)decode(request);
Assert.assertEquals(null, obj.getData());
Assert.assertEquals(true, obj.isTwoWay());
Assert.assertEquals(true, obj.isHeartbeat());
Assert.assertEquals("2.0.0", obj.getVersion());
System.out.println(obj);
}
@Test
public void test_Decode_Return_Request_Object() throws IOException{
//|10011111|20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, (byte) 0xdf, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Request obj = (Request)decode(request);
Assert.assertEquals(person, obj.getData());
Assert.assertEquals(true, obj.isTwoWay());
Assert.assertEquals(false, obj.isHeartbeat());
Assert.assertEquals("2.0.0", obj.getVersion());
System.out.println(obj);
}
@Test
public void test_Decode_Error_Request_Object() throws IOException{
//00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, (byte)0xdf, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
//bad object
byte[] badbytes = new byte[]{-1,-2,-3,-4,-3,-4,-3,-4,-3,-4,-3,-4};
System.arraycopy(badbytes, 0, request, 21, badbytes.length);
Request obj = (Request)decode(request);
Assert.assertEquals(true, obj.isBroken());
Assert.assertEquals(true, obj.getData() instanceof Throwable);
}
@Test
public void test_Header_Response_NoSerializationFlag() throws IOException{
//00000010-response/oneway/hearbeat=false/noset |20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 0, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Response obj = (Response)decode(request);
Assert.assertEquals(20, obj.getStatus());
Assert.assertEquals(person, obj.getResult());
System.out.println(obj);
}
@Test
public void test_Header_Response_Heartbeat() throws IOException{
//00000010-response/oneway/hearbeat=true |20-stats=ok|id=0|length=0
byte[] header = new byte[] { MAGIC_HIGH, MAGIC_LOW, 0x20, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
Person person = new Person();
byte[] request = getRequestBytes(person, header);
Response obj = (Response)decode(request);
Assert.assertEquals(20, obj.getStatus());
Assert.assertEquals(person, obj.getResult());
System.out.println(obj);
}
@Test
public void test_Encode_Request() throws IOException{
ChannelBuffer encodeBuffer = ChannelBuffers.dynamicBuffer(2014);
Channel channel = getCliendSideChannel(url);
Request request = new Request();
Person person = new Person();
request.setData(person);
codec.encode(channel, encodeBuffer, request);
//encode resault check need decode
byte[] data = new byte[encodeBuffer.writerIndex()];
encodeBuffer.readBytes(data);
ChannelBuffer decodeBuffer = ChannelBuffers.wrappedBuffer(data);
Request obj = (Request)codec.decode(channel, decodeBuffer);
Assert.assertEquals(request.isBroken(), obj.isBroken());
Assert.assertEquals(request.isHeartbeat(), obj.isHeartbeat());
Assert.assertEquals(request.isTwoWay(), obj.isTwoWay());
Assert.assertEquals(person, obj.getData());
}
@Test
public void test_Encode_Response() throws IOException{
ChannelBuffer encodeBuffer = ChannelBuffers.dynamicBuffer(1024);
Channel channel = getCliendSideChannel(url);
Response response = new Response();
response.setHeartbeat(true);
response.setId(1001l);
response.setStatus((byte)20 );
response.setVersion("11");
Person person = new Person();
response.setResult(person);
codec.encode(channel, encodeBuffer, response);
byte[] data = new byte[encodeBuffer.writerIndex()];
encodeBuffer.readBytes(data);
//encode resault check need decode
ChannelBuffer decodeBuffer = ChannelBuffers.wrappedBuffer(data);
Response obj = (Response)codec.decode(channel, decodeBuffer);
Assert.assertEquals(response.getId(), obj.getId());
Assert.assertEquals(response.getStatus(), obj.getStatus());
Assert.assertEquals(response.isHeartbeat(), obj.isHeartbeat());
Assert.assertEquals(person, obj.getResult());
// encode response verson ??
// Assert.assertEquals(response.getVersion(), obj.getVersion());
}
@Test
public void test_Encode_Error_Response() throws IOException{
ChannelBuffer encodeBuffer = ChannelBuffers.dynamicBuffer(1024);
Channel channel = getCliendSideChannel(url);
Response response = new Response();
response.setHeartbeat(true);
response.setId(1001l);
response.setStatus((byte)10 );
response.setVersion("11");
String badString = "bad" ;
response.setErrorMessage(badString);
Person person = new Person();
response.setResult(person);
codec.encode(channel, encodeBuffer, response);
byte[] data = new byte[encodeBuffer.writerIndex()];
encodeBuffer.readBytes(data);
//encode resault check need decode
ChannelBuffer decodeBuffer = ChannelBuffers.wrappedBuffer(data);
Response obj = (Response)codec.decode(channel, decodeBuffer);
Assert.assertEquals(response.getId(), obj.getId());
Assert.assertEquals(response.getStatus(), obj.getStatus());
Assert.assertEquals(response.isHeartbeat(), obj.isHeartbeat());
Assert.assertEquals(badString, obj.getErrorMessage());
Assert.assertEquals(null, obj.getResult());
// Assert.assertEquals(response.getVersion(), obj.getVersion());
}
// http://code.alibabatech.com/jira/browse/DUBBO-392
@Test
public void testMessageLengthGreaterThanMessageActualLength() throws Exception {
Channel channel = getCliendSideChannel(url);
Request request = new Request(1L);
request.setVersion("2.0.0");
Date date = new Date();
request.setData(date);
ChannelBuffer encodeBuffer = ChannelBuffers.dynamicBuffer(1024);
codec.encode(channel, encodeBuffer, request);
byte[] bytes = new byte[encodeBuffer.writerIndex()];
encodeBuffer.readBytes(bytes);
int len = Bytes.bytes2int(bytes, 12);
ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
out.write(bytes, 0, 12);
/*
* 填充长度不能低于256,hessian每次默认会从流中读取256个byte.
* 参见 Hessian2Input.readBuffer
*/
int padding = 512;
out.write(Bytes.int2bytes(len + padding));
out.write(bytes, 16, bytes.length - 16);
for(int i = 0; i < padding; i++) {
out.write(1);
}
out.write(bytes);
/* request|1111...|request */
ChannelBuffer decodeBuffer = ChannelBuffers.wrappedBuffer(out.toByteArray());
Request decodedRequest = (Request)codec.decode(channel, decodeBuffer);
Assert.assertTrue(date.equals(decodedRequest.getData()));
Assert.assertEquals(bytes.length + padding, decodeBuffer.readerIndex());
decodedRequest = (Request)codec.decode(channel, decodeBuffer);
Assert.assertTrue(date.equals(decodedRequest.getData()));
}
@Test
public void testMessageLengthExceedPayloadLimitWhenEncode() throws Exception {
Request request = new Request(1L);
request.setData("hello");
ChannelBuffer encodeBuffer = ChannelBuffers.dynamicBuffer(512);
AbstractMockChannel channel = getCliendSideChannel(url.addParameter(Constants.PAYLOAD_KEY, 4));
try {
codec.encode(channel, encodeBuffer, request);
Assert.fail();
} catch (IOException e) {
Assert.assertTrue(e.getMessage().startsWith("Data length too large: " + 6));
}
Response response = new Response(1L);
response.setResult("hello");
encodeBuffer = ChannelBuffers.dynamicBuffer(512);
channel = getServerSideChannel(url.addParameter(Constants.PAYLOAD_KEY, 4));
codec.encode(channel, encodeBuffer, response);
Assert.assertTrue(channel.getReceivedMessage() instanceof Response);
Response receiveMessage = (Response) channel.getReceivedMessage();
Assert.assertEquals(Response.BAD_RESPONSE, receiveMessage.getStatus());
Assert.assertTrue(receiveMessage.getErrorMessage().contains("Data length too large: "));
}
}