/* * Copyright 2014-2016 CyberVision, Inc. * * 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 org.kaaproject.kaa.common.kaatcp; import org.junit.Assert; import org.junit.Test; import org.kaaproject.kaa.common.avro.AvroByteArrayConverter; import org.kaaproject.kaa.common.channels.protocols.kaatcp.KaaTcpProtocolException; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.ConnAckListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.ConnectListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.DisconnectListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.PingRequestListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.PingResponseListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.SyncRequestListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.listeners.SyncResponseListener; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.ConnAck; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.ConnAck.ReturnCode; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Connect; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Disconnect; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.Disconnect.DisconnectReason; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.MessageFactory; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.PingRequest; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.PingResponse; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.SyncRequest; import org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.SyncResponse; import org.kaaproject.kaa.common.endpoint.gen.SyncRequestMetaData; import org.kaaproject.kaa.common.endpoint.security.KeyUtil; import org.kaaproject.kaa.common.endpoint.security.MessageEncoderDecoder; import org.kaaproject.kaa.common.hash.Sha1HashUtils; import org.mockito.Mockito; import java.io.IOException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.security.KeyPair; import java.util.Arrays; public class MessageFactoryTest { @Test public void testConnackMessage() throws KaaTcpProtocolException { MessageFactory factory = new MessageFactory(); byte[] rawConnack = new byte[]{0x20, 0x02, 0x00, 0x03}; factory.getFramer().pushBytes(rawConnack); ConnAckListener idRejectListener = Mockito.spy(new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.REFUSE_ID_REJECT, message.getReturnCode()); } }); factory.registerMessageListener(idRejectListener); factory.getFramer().pushBytes(rawConnack); Mockito.verify(idRejectListener, Mockito.times(1)).onMessage(Mockito.any(ConnAck.class)); byte[] rawConnackAccept = new byte[]{0x20, 0x02, 0x00, 0x01}; ConnAckListener acceptListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.ACCEPTED, message.getReturnCode()); } }; factory.registerMessageListener(acceptListener); factory.getFramer().pushBytes(rawConnackAccept); byte[] rawConnackBadProto = new byte[]{0x20, 0x02, 0x00, 0x02}; ConnAckListener badProtoListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.REFUSE_BAD_PROTOCOL, message.getReturnCode()); } }; factory.registerMessageListener(badProtoListener); factory.getFramer().pushBytes(rawConnackBadProto); byte[] rawConnackServerUnavailable = new byte[]{0x20, 0x02, 0x00, 0x04}; ConnAckListener serverUnavailableListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.REFUSE_SERVER_UNAVAILABLE, message.getReturnCode()); } }; factory.registerMessageListener(serverUnavailableListener); factory.getFramer().pushBytes(rawConnackServerUnavailable); byte[] rawConnackBadCredentials = new byte[]{0x20, 0x02, 0x00, 0x05}; ConnAckListener badCredentialsListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.REFUSE_BAD_CREDENTIALS, message.getReturnCode()); } }; factory.registerMessageListener(badCredentialsListener); factory.getFramer().pushBytes(rawConnackBadCredentials); byte[] rawConnackNoAuth = new byte[]{0x20, 0x02, 0x00, 0x06}; ConnAckListener noAuthListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.REFUSE_NO_AUTH, message.getReturnCode()); } }; factory.registerMessageListener(noAuthListener); factory.getFramer().pushBytes(rawConnackNoAuth); byte[] rawConnackUndefined = new byte[]{0x20, 0x02, 0x00, 0x07}; ConnAckListener undefinedListener = new ConnAckListener() { @Override public void onMessage(ConnAck message) { Assert.assertEquals(ReturnCode.UNDEFINED, message.getReturnCode()); } }; factory.registerMessageListener(undefinedListener); factory.getFramer().pushBytes(rawConnackUndefined); } @Test public void testConnectMessage() throws KaaTcpProtocolException, IOException, GeneralSecurityException { KeyPair clientPair = KeyUtil.generateKeyPair(); KeyPair serverPair = KeyUtil.generateKeyPair(); MessageEncoderDecoder crypt = new MessageEncoderDecoder(clientPair.getPrivate(), clientPair.getPublic(), serverPair.getPublic()); AvroByteArrayConverter<org.kaaproject.kaa.common.endpoint.gen.SyncRequest> requestConverter = new AvroByteArrayConverter<>(org.kaaproject.kaa.common.endpoint.gen.SyncRequest.class); org.kaaproject.kaa.common.endpoint.gen.SyncRequest request = new org.kaaproject.kaa.common.endpoint.gen.SyncRequest(); request.setRequestId(42); SyncRequestMetaData md = new SyncRequestMetaData(); md.setSdkToken("sdkToken"); md.setEndpointPublicKeyHash(ByteBuffer.wrap(Sha1HashUtils.hashToBytes(clientPair.getPublic().getEncoded()))); request.setSyncRequestMetaData(md); byte[] rawData = requestConverter.toByteArray(request); final byte[] payload = crypt.encodeData(rawData); final byte[] sessionKey = crypt.getEncodedSessionKey(); final byte[] signature = crypt.sign(sessionKey); final byte connectHeader[] = new byte[]{0x10, (byte) 0xC2, 0x04, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x02, (byte) 0xf2, (byte) 0x91, (byte) 0xf2, (byte) 0xd4, 0x11, 0x01, 0x00, (byte) 0xC8}; ByteBuffer connectBuffer = ByteBuffer.allocate(sessionKey.length + signature.length + payload.length + 21); connectBuffer.put(connectHeader); connectBuffer.put(sessionKey); connectBuffer.put(signature); connectBuffer.put(payload); connectBuffer.position(0); MessageFactory factory = new MessageFactory(); factory.getFramer().pushBytes(connectBuffer.array()); ConnectListener listener = Mockito.spy(new ConnectListener() { @Override public void onMessage(Connect message) { Assert.assertEquals(200, message.getKeepAlive()); Assert.assertArrayEquals(signature, message.getSignature()); Assert.assertArrayEquals(sessionKey, message.getAesSessionKey()); Assert.assertArrayEquals(payload, message.getSyncRequest()); Assert.assertEquals(0xf291f2d4, message.getNextProtocolId()); } }); factory.registerMessageListener(listener); factory.getFramer().pushBytes(connectBuffer.array()); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(Connect.class)); } @Test public void testConnectMessageWithoutKey() throws KaaTcpProtocolException, IOException, GeneralSecurityException { KeyPair clientPair = KeyUtil.generateKeyPair(); AvroByteArrayConverter<org.kaaproject.kaa.common.endpoint.gen.SyncRequest> requestConverter = new AvroByteArrayConverter<>(org.kaaproject.kaa.common.endpoint.gen.SyncRequest.class); org.kaaproject.kaa.common.endpoint.gen.SyncRequest request = new org.kaaproject.kaa.common.endpoint.gen.SyncRequest(); request.setRequestId(42); SyncRequestMetaData md = new SyncRequestMetaData(); md.setSdkToken("sdkToken"); md.setEndpointPublicKeyHash(ByteBuffer.wrap(Sha1HashUtils.hashToBytes(clientPair.getPublic().getEncoded()))); request.setSyncRequestMetaData(md); final byte[] rawData = requestConverter.toByteArray(request); //we assume that size of rawdata is less then 128 here final byte connectHeader[] = new byte[]{0x10, (byte) (rawData.length + 18), 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x02, (byte) 0xf2, (byte) 0x91, (byte) 0xf2, (byte) 0xd4, 0x00, 0x00, 0x00, (byte) 0xC8}; ByteBuffer connectBuffer = ByteBuffer.allocate(rawData.length + 20); connectBuffer.put(connectHeader); connectBuffer.put(rawData); connectBuffer.position(0); MessageFactory factory = new MessageFactory(); factory.getFramer().pushBytes(connectBuffer.array()); ConnectListener listener = Mockito.spy(new ConnectListener() { @Override public void onMessage(Connect message) { Assert.assertEquals(200, message.getKeepAlive()); Assert.assertEquals(0xf291f2d4, message.getNextProtocolId()); System.out.println(Arrays.toString(message.getSyncRequest())); Assert.assertArrayEquals(rawData, message.getSyncRequest()); } }); factory.registerMessageListener(listener); factory.getFramer().pushBytes(connectBuffer.array()); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(Connect.class)); } @Test public void testSyncResponse() throws KaaTcpProtocolException { final byte syncResponse[] = new byte[]{(byte) 0xF0, 0x0D, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x00, 0x05, 0x14, (byte) 0xFF}; MessageFactory factory = new MessageFactory(); SyncResponseListener listener = Mockito.spy(new SyncResponseListener() { @Override public void onMessage(SyncResponse message) { Assert.assertEquals(1, message.getAvroObject().length); Assert.assertEquals(0xFF, message.getAvroObject()[0] & 0xFF); Assert.assertEquals(5, message.getMessageId()); Assert.assertEquals(false, message.isZipped()); Assert.assertEquals(true, message.isEncrypted()); Assert.assertEquals(false, message.isRequest()); } }); factory.registerMessageListener(listener); factory.getFramer().pushBytes(syncResponse); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(SyncResponse.class)); } @Test public void testSyncRequest() throws KaaTcpProtocolException { final byte syncRequest[] = new byte[]{(byte) 0xF0, 0x0D, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x00, 0x05, 0x15, (byte) 0xFF}; MessageFactory factory = new MessageFactory(); SyncRequestListener listener = Mockito.spy(new SyncRequestListener() { @Override public void onMessage(SyncRequest message) { Assert.assertEquals(1, message.getAvroObject().length); Assert.assertEquals(0xFF, message.getAvroObject()[0] & 0xFF); Assert.assertEquals(5, message.getMessageId()); Assert.assertEquals(false, message.isZipped()); Assert.assertEquals(true, message.isEncrypted()); Assert.assertEquals(true, message.isRequest()); } }); factory.registerMessageListener(listener); factory.getFramer().pushBytes(syncRequest); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(SyncRequest.class)); } @Test public void testPingRequest() throws KaaTcpProtocolException { final byte[] pingRequest = new byte[]{(byte) 0xC0, 0x00}; MessageFactory factory = new MessageFactory(); factory.getFramer().pushBytes(pingRequest); PingRequestListener listener = Mockito.mock(PingRequestListener.class); factory.registerMessageListener(listener); factory.getFramer().pushBytes(pingRequest); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(PingRequest.class)); } @Test public void testPingResponse() throws KaaTcpProtocolException { final byte[] pingResponse = new byte[]{(byte) 0xD0, 0x00}; MessageFactory factory = new MessageFactory(); factory.getFramer().pushBytes(pingResponse); PingResponseListener listener = Mockito.mock(PingResponseListener.class); factory.registerMessageListener(listener); factory.getFramer().pushBytes(pingResponse); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(PingResponse.class)); } @Test public void testDisconnect() throws KaaTcpProtocolException { final byte[] disconnect = new byte[]{(byte) 0xE0, 0x02, 0x00, 0x02}; MessageFactory factory = new MessageFactory(); factory.getFramer().pushBytes(disconnect); DisconnectListener listener = Mockito.spy(new DisconnectListener() { @Override public void onMessage(Disconnect message) { Assert.assertEquals(DisconnectReason.INTERNAL_ERROR, message.getReason()); } }); factory.registerMessageListener(listener); factory.getFramer().pushBytes(disconnect); Mockito.verify(listener, Mockito.times(1)).onMessage(Mockito.any(Disconnect.class)); } @Test public void testBytesPartialPush() throws KaaTcpProtocolException { final byte syncRequest[] = new byte[]{(byte) 0xF0, 0x0D, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x00, 0x05, 0x15, (byte) 0xFF}; final byte syncRequest2[] = new byte[]{(byte) 0xF0, 0x0D, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x00, 0x05, 0x15, (byte) 0xFF}; final byte syncRequest3[] = new byte[]{(byte) 0xF0, 0x0D, 0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', 0x01, 0x00, 0x05, 0x15, (byte) 0xFF}; int totalLength = syncRequest.length + syncRequest2.length + syncRequest3.length; ByteBuffer totalBuffer = ByteBuffer.allocate(totalLength); totalBuffer.put(syncRequest); totalBuffer.put(syncRequest2); totalBuffer.put(syncRequest3); totalBuffer.position(0); byte[] firstBuffer = new byte[syncRequest.length - 2]; totalBuffer.get(firstBuffer); byte[] secondBuffer = new byte[syncRequest2.length + 4]; totalBuffer.get(secondBuffer); byte[] thirdBuffer = new byte[syncRequest3.length - 2]; totalBuffer.get(thirdBuffer); MessageFactory factory = new MessageFactory(); SyncRequestListener syncRequestListener = Mockito.spy(new SyncRequestListener() { @Override public void onMessage(SyncRequest message) { Assert.assertEquals(1, message.getAvroObject().length); Assert.assertEquals(0xFF, message.getAvroObject()[0] & 0xFF); Assert.assertEquals(5, message.getMessageId()); Assert.assertEquals(false, message.isZipped()); Assert.assertEquals(true, message.isEncrypted()); Assert.assertEquals(true, message.isRequest()); } }); factory.registerMessageListener(syncRequestListener); int i = factory.getFramer().pushBytes(firstBuffer); Assert.assertEquals(firstBuffer.length, i); i = factory.getFramer().pushBytes(secondBuffer); Assert.assertEquals(secondBuffer.length, i); i = factory.getFramer().pushBytes(thirdBuffer); Assert.assertEquals(thirdBuffer.length, i); Mockito.verify(syncRequestListener, Mockito.times(3)).onMessage(Mockito.any(SyncRequest.class)); } }