/* * 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.channels.protocols.kaatcp.messages; import org.kaaproject.kaa.common.channels.protocols.kaatcp.KaaTcpProtocolException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Connect message Class. When a TCP/IP socket connection is established from a client to a server, * a protocol level session must be created using a CONNECT flow. Variable header Protocol Name byte * 1 Length MSB (0) byte 2 Length LSB (6) byte 3 K byte 4 a byte 5 a byte 6 t byte 7 c byte 8 * p Protocol version byte 9 Version (1) Connect Flags byte 10 User name flag (0) Password flag * (0) Will RETAIN (0) Will QoS (00) Will flag (0) Clean Session (1) 0x02 - value Keep Alive timer * byte 11 Keep alive MSB (0) byte 12 Keep alive LSB (200) Keep Alive timer - default value 200 * seconds. * Payload: Session Key: AES Session encoding key (16 byte) - encrypted with the Operations server * RSA Public Key EndpointPublicKeyHash: SHA Hash of Endpoint Public Key (32 byte) Signature: RSA * signature (32 byte) signed with the Endpoint Private Key of Session key (16 byte) + * EndpointPublicKeyHash (32 byte) * * @author Andrey Panasenko */ public class Connect extends MqttFrame { public static final Logger LOG = LoggerFactory //NOSONAR .getLogger(Connect.class); public static final int CONNECT_VERIABLE_HEADER_LENGTH_V1 = 18; public static final int CONNECT_AES_SESSION_KEY_LENGTH = 256; public static final int CONNECT_SIGNATURE_LENGTH = 256; public static final byte CONNECT_VERSION = 0x01; public static final byte CONNECT_FIXED_HEADER_FLAG = 0x02; public static final byte CONNECT_SESSION_KEY_FLAGS = 0x11; public static final byte CONNECT_SIGNATURE_FLAGS = 0x01; private static final byte[] FIXED_HEADER_CONST = new byte[]{0x00, 0x06, 'K', 'a', 'a', 't', 'c', 'p', CONNECT_VERSION, CONNECT_FIXED_HEADER_FLAG}; /** * kaatcp keep alive interval, default 200 seconds. */ private int keepAlive = 200; /** * The next protocol identifier. */ private int nextProtocolId; /** * AES session key. */ private byte[] aesSessionKey; /** * Signature of aesSessionKey and endpointPublicKeyHash. */ private byte[] signature; /** * SyncRequest in Connect message. */ private byte[] syncRequest; private boolean hasSignature = false; private boolean hasAesSessionKey = false; /** * Default Constructor. * * @param keepAlive the keep alive in seconds, max value 65535 seconds. * @param nextProtocolId the next protocol id * @param aesSessionKey the byte[] of AES session key, length 16 byte. * @param syncRequest the byte[] of Avro SyncRequest object * @param signature the byte[] of Signature of aesSessionKey and endpointPublicKeyHash, * length 32 byte. */ public Connect(int keepAlive, int nextProtocolId, byte[] aesSessionKey, byte[] syncRequest, byte[] signature) { setMessageType(MessageType.CONNECT); this.setKeepAlive(keepAlive); this.setNextProtocolId(nextProtocolId); this.setAesSessionKey(aesSessionKey); this.setSyncRequest(syncRequest); this.setSignature(signature); remainingLength = CONNECT_VERIABLE_HEADER_LENGTH_V1; if (aesSessionKey != null) { remainingLength += CONNECT_AES_SESSION_KEY_LENGTH; } if (signature != null) { remainingLength += CONNECT_SIGNATURE_LENGTH; } if (syncRequest != null) { remainingLength += syncRequest.length; } LOG.debug("Created Connect message: session key size = {}, signature size = {}," + " sync request size = {}", aesSessionKey != null ? aesSessionKey.length : "null", signature != null ? signature.length : "null", syncRequest != null ? syncRequest.length : "null"); } /** * Establish connection. */ public Connect() { super(); setMessageType(MessageType.CONNECT); } /* (non-Javadoc) * @see org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.mqttFrame#pack(int) */ @Override protected void pack() { packVeriableHeader(); if (getAesSessionKey() != null) { buffer.put(getAesSessionKey()); } if (getSignature() != null) { buffer.put(getSignature()); } if (getSyncRequest() != null) { buffer.put(getSyncRequest()); } } /** * Pack Connect variable header. * * @return - number of packed bytes. */ private void packVeriableHeader() { buffer.put(FIXED_HEADER_CONST); buffer.putInt(nextProtocolId); if (getAesSessionKey() != null) { buffer.put(CONNECT_SESSION_KEY_FLAGS); } else { buffer.put((byte) 0); } if (getSignature() != null) { buffer.put(CONNECT_SIGNATURE_FLAGS); } else { buffer.put((byte) 0); } buffer.putChar((char) keepAlive); } /** * KeepAlive getter. * * @return int keepAlive */ public int getKeepAlive() { return keepAlive; } /** * KeepAlive setter. * * @param keepAlive int */ public void setKeepAlive(int keepAlive) { this.keepAlive = keepAlive; } /** * Next protocol ID getter. * * @return Next protocol ID int */ public int getNextProtocolId() { return nextProtocolId; } /** * Next protocol ID setter. * * @param nextProtocolId protocol ID int */ public void setNextProtocolId(int nextProtocolId) { this.nextProtocolId = nextProtocolId; } /** * AES Session Key getter. * * @return byte[] aesSessionKey */ public byte[] getAesSessionKey() { return aesSessionKey; } /** * AES Session Key setter. * * @param aesSessionKey byte[] */ public void setAesSessionKey(byte[] aesSessionKey) { this.aesSessionKey = aesSessionKey; if (aesSessionKey != null) { this.hasAesSessionKey = true; } } /** * Signature getter. * * @return byte[] signature */ public byte[] getSignature() { return signature; } /** * Signature setter. * * @param signature byte[] */ public void setSignature(byte[] signature) { this.signature = signature; if (signature != null) { this.hasSignature = true; } } /** * Returns a sync request. * * @return the syncRequest */ public byte[] getSyncRequest() { return syncRequest; } /** * Sets a sync request. * * @param syncRequest the syncRequest to set */ public void setSyncRequest(byte[] syncRequest) { this.syncRequest = syncRequest; } /* (non-Javadoc) * @see org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.mqttFrame#decode(byte[], int) */ @Override protected void decode() throws KaaTcpProtocolException { decodeVariableHeader(); nextProtocolId = buffer.getInt(); hasAesSessionKey = buffer.get() != 0; hasSignature = buffer.get() != 0; decodeKeepAlive(); if (hasAesSessionKey) { decodeSessionKey(); } if (hasSignature) { decodeSignature(); } decodeSyncRequest(); } private void decodeSyncRequest() { int syncRequestSize = buffer.capacity() - buffer.position(); if (syncRequestSize > 0) { syncRequest = new byte[syncRequestSize]; buffer.get(syncRequest); } } private void decodeSignature() { signature = new byte[CONNECT_SIGNATURE_LENGTH]; buffer.get(signature); } private void decodeSessionKey() { aesSessionKey = new byte[CONNECT_AES_SESSION_KEY_LENGTH]; buffer.get(aesSessionKey); } /** * Decode variable header fields. * * @throws KaaTcpProtocolException - if protocol version missmatch */ private void decodeVariableHeader() throws KaaTcpProtocolException { for (int i = 0; i < FIXED_HEADER_CONST.length; i++) { if (FIXED_HEADER_CONST[i] != buffer.get()) { throw new KaaTcpProtocolException("Kaatcp protocol version missmatch"); } } } private void decodeKeepAlive() { int msb = (buffer.get() & 0xFF) << 8; int lsb = buffer.get() & 0xFF; keepAlive = (msb | lsb); } /* (non-Javadoc) * @see * org.kaaproject.kaa.common.channels.protocols.kaatcp.messages.MqttFrame#isNeedCloseConnection() */ @Override public boolean isNeedCloseConnection() { return false; } public boolean hasSignature() { return hasSignature; } public boolean isEncrypted() { return hasAesSessionKey; } }