Changed lib structure to be modular

This commit is contained in:
selva.andre
2012-05-10 22:00:57 +02:00
parent 0dc403d278
commit 2cbf237887
75 changed files with 87 additions and 65 deletions
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
Per Mina
- per gestire l'inattività su una sessione usare
acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, 10 );
e
nell'implementazione del IoHandlerAdapter ridefinire il metodo
public void sessionIdle( IoSession session, IdleStatus status ) throws Exception
Glossario:
QOS: presente solo per i messaggi PUBLISH, definisce 3 livelli
QoS0: at most once, cioe' viene notificato al piu' uno (il che' significa che potrebbe anche non essere notificato) equivalente a messaggi non persistenti di JMS
QoS1: at least once, delivery equivalente ai messaggi persistenti di JMS
Q0S2: exactly once, delivery assicurato equivalente ai messagi persistenti di JMS + DUPS_OK_ACKNOWLEDGE
RETAIN: se true dice di persistere il messaggio dopo esser stato notificato a tutti i sottoscritori del topic. Se in un secondo momento un cliente si sottoscrive ad un messaggio pubblicato con RETAIN = 1 allora anche il messaggio di notifica dovrà avere RETAIN = 1, altrimenti non accade nulla. I messaggi notificati al momento del PUBLISH non hanno flag RETAIN = 1 serve solo per dire ai posteri che il messaggio che stanno ricevendo non è "live".
Un server PUO' eliminare un messaggio marcato con RETAIN se ne arriva un'altro sullo stesso topic con lunghezza = 0 e RETAIN = 1.
CLEAN SESSION: è un flag disponibile per il solo messaggio di CONNECT.Se = 0 allora il server DEVE memorizzare(mantenere) le sottoscrizioni del client dopo che si sia disconnesso. Questo prevede di continuare a salvare i messaggi QOS1 e QOS2 per i topic sottoscritti cosicche' possano essere consegnati quando il client si riconnetterà. Il server DEVE inoltre mantenere lo stato dei messaggi che stavano per essere consegnati prima che la connessione fosse persa. Questa informazione DEVE esser mantenuta finchè il client si ricconnetta. Se flag = 1 allora il server DEVE scartare qualsiasi informazione precedentemente memorizzata sul client e trattare la connessione come "pulita". Il server DEVE anche scartare qualunque stato qualora il client di disconnetta.
Solitamente, un client opererà o in un modo o nell'altro. La scelta dipenderà dall'applicazione. Un sessione cliente pulita non riceverà informazioni vecchie e DOVRA' ri-sottoscriversi ogni volta che si connette. Una sessione cliente NON pulita non perderà nessun messaggio di tipo QoS1 o QoS2 che sia stato pubblicato mentro esso era disconnesso. I messaggi di tipo QoS0 NON vengono mai salvati. Un server deve fornire un meccanismo di amministrazione per la pulizia delle informazione salvate su di un client e che possa essere usata qualora si sappia che il client non si riconnetterà.
+73
View File
@@ -0,0 +1,73 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<relativePath>../pom.xml</relativePath>
<artifactId>moquette-parent</artifactId>
<groupId>org.dna.mqtt</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>moquette-parser</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Moquette - Parser</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.mina</groupId>
<artifactId>mina-core</artifactId>
<version>2.0.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.2</version>
<scope>test</scope>
</dependency>
<!-- needs extra dependencies: objenesis & hamcrest -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>1.9.0</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludes>
<exclude>org/dna/mqtt/moquette/proto/TestUtils.java</exclude>
<exclude>org/dna/mqtt/moquette/proto/TestUtils$*</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,75 @@
package org.dna.mqtt.moquette.client;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder;
import org.apache.mina.filter.codec.demux.DemuxingProtocolEncoder;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.dna.mqtt.moquette.proto.ConnAckDecoder;
import org.dna.mqtt.moquette.proto.ConnectDecoder;
import org.dna.mqtt.moquette.proto.ConnectEncoder;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.dna.mqtt.moquette.server.Server;
/**
* Simple client to connect to a server
*
*
* @author andrea
*/
public class Client {
private static final Logger LOG = Logger.getLogger(Client.class.getName());
private static final String HOSTNAME = /*"localhost"*/ "127.0.0.1";
private static final int PORT = Server.PORT;
private static final long CONNECT_TIMEOUT = 30 * 1000L; // 30 seconds
public static void main(String[] args) throws Throwable {
DemuxingProtocolDecoder decoder = new DemuxingProtocolDecoder();
decoder.addMessageDecoder(new ConnAckDecoder());
DemuxingProtocolEncoder encoder = new DemuxingProtocolEncoder();
encoder.addMessageEncoder(ConnectMessage.class, new ConnectEncoder());
NioSocketConnector connector = new NioSocketConnector();
// Configure the service.
connector.setConnectTimeoutMillis(CONNECT_TIMEOUT);
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(encoder, decoder));
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.setHandler(new ClientMQTTHandler());
IoSession session;
for (;;) {
try {
ConnectFuture future = connector.connect(new InetSocketAddress(HOSTNAME, PORT));
LOG.info("Client waiting to connect to server");
future.awaitUninterruptibly();
session = future.getSession();
break;
} catch (RuntimeIoException e) {
System.err.println("Failed to connect.");
e.printStackTrace();
Thread.sleep(5000);
}
}
//send a message over the session
ConnectMessage connMsg = new ConnectMessage();
connMsg.setKeepAlive(3);
session.write(connMsg);
LOG.info("Client wrote message to server");
// wait until the summation is done
session.getCloseFuture().awaitUninterruptibly();
connector.dispose();
}
}
@@ -0,0 +1,12 @@
package org.dna.mqtt.moquette.client;
import org.apache.mina.core.service.IoHandlerAdapter;
/**
*
* @author andrea
*/
public class ClientMQTTHandler extends IoHandlerAdapter {
// public void sessionOpened(IoSession session) {}
// public void messageReceived(IoSession session, Object message) {
}
@@ -0,0 +1,20 @@
package org.dna.mqtt.moquette.messaging.spi;
/**
* Interface to the underling messaging system used to publish, subscribe.
*
* @author andrea
*/
public class IMessaging {
/**
* Publish a message on the messaging system. The qos is the level of quality
* the retain flag is true iff the message should be persisted for future
* notification also after all the current subscriber to the given topic has
* been notified.
*/
public void publish(String topic, Object message, byte qos, boolean retain) {
throw new UnsupportedOperationException("Not yet implemented");
}
}
@@ -0,0 +1,35 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
/**
*
* @author andrea
*/
public class ConnAckDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.CONNACK, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
ConnAckMessage message = new ConnAckMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
//skip reserved byte
in.skip(1);
//read return code
message.setReturnCode(in.get());
out.write(message);
return OK;
}
}
@@ -0,0 +1,24 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
/**
*
* @author andrea
*/
public class ConnAckEncoder implements MessageEncoder<ConnAckMessage> {
public void encode(IoSession session, ConnAckMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.CONNACK << 4));
buff.put(Utils.encodeRemainingLength(2));
buff.put((byte) 0);
buff.put(message.getReturnCode()).flip();
out.write(buff);
}
}
@@ -0,0 +1,138 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
/**
* Decoder for the CONNECT message
*
* @author andrea
*/
public class ConnectDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.CONNECT, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
ConnectMessage message = new ConnectMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
int remainingLength = message.getRemainingLength();
int start = in.position();
//Connect specific decoding part
//ProtocolName 8 bytes
if (in.remaining() < 12) {
return NEED_DATA;
}
byte[] encProtoName = new byte[6];
in.skip(2); //size, is 0x06
in.get(encProtoName);
String protoName = new String(encProtoName, "UTF-8");
if (!"MQIsdp".equals(protoName)) {
return NOT_OK;
}
message.setProtocolName(protoName);
//ProtocolVersion 1 byte (value 0x03)
message.setProcotolVersion(in.get());
//Connection flag
byte connFlags = in.get();
boolean cleanSession = ((connFlags & 0x02) >> 1) == 1 ? true : false;
boolean willFlag = ((connFlags & 0x04) >> 2) == 1 ? true : false;
byte willQos = (byte) ((connFlags & 0x18) >> 3);
if (willQos > 2) {
return NOT_OK; //admitted values for willqos are 0, 1, 2
}
boolean willRetain = ((connFlags & 0x20) >> 5) == 1 ? true : false;
boolean passwordFlag = ((connFlags & 0x40) >> 6) == 1 ? true : false;
boolean userFlag = ((connFlags & 0x80) >> 7) == 1 ? true : false;
//a password is true iff user is true.
if (!userFlag && passwordFlag) {
return NOT_OK;
}
message.setCleanSession(cleanSession);
message.setWillFlag(willFlag);
message.setWillQos(willQos);
message.setWillRetain(willRetain);
message.setPasswordFlag(passwordFlag);
message.setUserFlag(userFlag);
//Keep Alive timer 2 bytes
int keepAlive = Utils.readWord(in);
message.setKeepAlive(keepAlive);
if (remainingLength == 12) {
out.write(message);
return OK;
}
//Decode the ClientID
String clientID = Utils.decodeString(in);
if (clientID == null) {
return NEED_DATA;
}
message.setClientID(clientID);
//Decode willTopic
if (willFlag) {
String willTopic = Utils.decodeString(in);
if (willTopic == null) {
return NEED_DATA;
}
message.setWillTopic(willTopic);
}
//Decode willMessage
if (willFlag) {
String willMessage = Utils.decodeString(in);
if (willMessage == null) {
return NEED_DATA;
}
message.setWillMessage(willMessage);
}
//Compatibility check wieth v3.0, remaining length has precedence over
//the user and password flags
int readed = in.position() - start;
if (readed == remainingLength) {
out.write(message);
return OK;
}
//Decode username
if (userFlag) {
String userName = Utils.decodeString(in);
if (userName == null) {
return NEED_DATA;
}
message.setUsername(userName);
}
readed = in.position() - start;
if (readed == remainingLength) {
out.write(message);
return OK;
}
//Decode password
if (passwordFlag) {
String password = Utils.decodeString(in);
if (password == null) {
return NEED_DATA;
}
message.setPassword(password);
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,72 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
/**
*
* @author andrea
*/
public class ConnectEncoder implements MessageEncoder<ConnectMessage> {
public void encode(IoSession session, ConnectMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer staticHeaderBuff = IoBuffer.allocate(12);
staticHeaderBuff.put(Utils.encodeString("MQIsdp"));
//version
staticHeaderBuff.put((byte)0x03);
//connection flags and Strings
byte connectionFlags = 0;
if (message.isCleanSession()) {
connectionFlags |= 0x02;
}
if (message.isWillFlag()) {
connectionFlags |= 0x04;
}
connectionFlags |= ((message.getWillQos() & 0x03) << 3);
if (message.isWillRetain()) {
connectionFlags |= 0x020;
}
if (message.isPasswordFlag()) {
connectionFlags |= 0x040;
}
if (message.isUserFlag()) {
connectionFlags |= 0x080;
}
staticHeaderBuff.put(connectionFlags);
//Keep alive timer
Utils.writeWord(staticHeaderBuff, message.getKeepAlive());
staticHeaderBuff.flip();
//Variable part
IoBuffer variableHeaderBuff = IoBuffer.allocate(12).setAutoExpand(true);
if (message.getClientID() != null) {
variableHeaderBuff.put(Utils.encodeString(message.getClientID()));
if (message.isWillFlag()) {
variableHeaderBuff.put(Utils.encodeString(message.getWillTopic()));
variableHeaderBuff.put(Utils.encodeString(message.getWillMessage()));
}
if (message.isUserFlag() && message.getUsername() != null) {
variableHeaderBuff.put(Utils.encodeString(message.getUsername()));
if (message.isPasswordFlag() && message.getPassword() != null) {
variableHeaderBuff.put(Utils.encodeString(message.getPassword()));
}
}
}
variableHeaderBuff.flip();
int variableHeaderSize = variableHeaderBuff.remaining();
IoBuffer buff = IoBuffer.allocate(14 + variableHeaderSize);
buff.put((byte) (AbstractMessage.CONNECT << 4));
buff.put(Utils.encodeRemainingLength(12 + variableHeaderSize));
buff.put(staticHeaderBuff).put(variableHeaderBuff).flip();
out.write(buff);
}
}
@@ -0,0 +1,29 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.DisconnectMessage;
/**
*
* @author andrea
*/
public class DisconnectDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.DISCONNECT, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
DisconnectMessage message = new DisconnectMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,22 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.DisconnectMessage;
/**
*
* @author andrea
*/
public class DisconnectEncoder implements MessageEncoder<DisconnectMessage> {
public void encode(IoSession session, DisconnectMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(2);
buff.put((byte) (AbstractMessage.DISCONNECT << 4)).put((byte)0).flip();
out.write(buff);
}
}
@@ -0,0 +1,28 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
/**
*
* @author andrea
*/
public abstract class MessageIDDecoder extends MqttDecoder {
protected abstract MessageIDMessage createMessage();
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
MessageIDMessage message = createMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
//read messageIDs
message.setMessageID(Utils.readWord(in));
out.write(message);
return OK;
}
}
@@ -0,0 +1,37 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoderAdapter;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
/**
* Contains common code for all mqtt decoders.
*
* @author andrea
*/
public abstract class MqttDecoder extends MessageDecoderAdapter {
protected MessageDecoderResult decodeCommonHeader(AbstractMessage message, IoBuffer in) {
//Common decoding part
if (in.remaining() < 2) {
return NEED_DATA;
}
byte h1 = in.get();
byte messageType = (byte) ((h1 & 0x0010) >> 4);
boolean dupFlag = ((byte) ((h1 & 0x0008) >> 3) == 1);
byte qosLevel = (byte) ((h1 & 0x0006) >> 1);
boolean retainFlag = ((byte) (h1 & 0x0001) == 1);
int remainingLength = Utils.decodeRemainingLenght(in);
if (remainingLength == -1) {
return NEED_DATA;
}
message.setMessageType(messageType);
message.setDupFlag(dupFlag);
message.setQos(AbstractMessage.QOSType.values()[qosLevel]);
message.setRetainFlag(retainFlag);
message.setRemainingLength(remainingLength);
return OK;
}
}
@@ -0,0 +1,29 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PingReqMessage;
/**
*
* @author andrea
*/
public class PingReqDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PINGREQ, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
PingReqMessage message = new PingReqMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,21 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PingReqMessage;
/**
*
* @author andrea
*/
public class PingReqEncoder implements MessageEncoder<PingReqMessage> {
public void encode(IoSession session, PingReqMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(2);
buff.put((byte) (AbstractMessage.PINGREQ << 4)).put((byte)0).flip();
out.write(buff);
}
}
@@ -0,0 +1,33 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PingRespMessage;
/**
*
* @author andrea
*/
public class PingRespDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PINGRESP, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
PingRespMessage message = new PingRespMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,21 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PingRespMessage;
/**
*
* @author andrea
*/
public class PingRespEncoder implements MessageEncoder<PingRespMessage> {
public void encode(IoSession session, PingRespMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(2);
buff.put((byte) (AbstractMessage.PINGRESP << 4)).put((byte)0).flip();
out.write(buff);
}
}
@@ -0,0 +1,25 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
import org.dna.mqtt.moquette.proto.messages.PubAckMessage;
/**
*
* @author andrea
*/
public class PubAckDecoder extends MessageIDDecoder {
@Override
protected MessageIDMessage createMessage() {
return new PubAckMessage();
}
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PUBACK, in);
}
}
@@ -0,0 +1,23 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PubAckMessage;
/**
*
* @author andrea
*/
public class PubAckEncoder implements MessageEncoder<PubAckMessage> {
public void encode(IoSession session, PubAckMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.PUBACK << 4));
buff.put(Utils.encodeRemainingLength(2));
Utils.writeWord(buff, message.getMessageID());
buff.flip();
out.write(buff);
}
}
@@ -0,0 +1,24 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
/**
*
* @author andrea
*/
public class PubCompDecoder extends MessageIDDecoder {
@Override
protected MessageIDMessage createMessage() {
return new PubCompMessage();
}
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PUBCOMP, in);
}
}
@@ -0,0 +1,22 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
/**
*
* @author andrea
*/
public class PubCompEncoder implements MessageEncoder<PubCompMessage> {
public void encode(IoSession session, PubCompMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.PUBCOMP << 4));
buff.put(Utils.encodeRemainingLength(2));
Utils.writeWord(buff, message.getMessageID());
buff.flip();
out.write(buff);
}
}
@@ -0,0 +1,11 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
/**
*
* @author andrea
*/
public class PubCompMessage extends MessageIDMessage {
}
@@ -0,0 +1,25 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
import org.dna.mqtt.moquette.proto.messages.PubRecMessage;
/**
*
* @author andrea
*/
public class PubRecDecoder extends MessageIDDecoder {
@Override
protected MessageIDMessage createMessage() {
return new PubRecMessage();
}
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PUBREC, in);
}
}
@@ -0,0 +1,23 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PubRecMessage;
/**
*
* @author andrea
*/
public class PubRecEncoder implements MessageEncoder<PubRecMessage> {
public void encode(IoSession session, PubRecMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.PUBREC << 4));
buff.put(Utils.encodeRemainingLength(2));
Utils.writeWord(buff, message.getMessageID());
buff.flip();
out.write(buff);
}
}
@@ -0,0 +1,26 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
import org.dna.mqtt.moquette.proto.messages.PubRelMessage;
/**
*
* @author andrea
*/
public class PubRelDecoder extends MessageIDDecoder {
@Override
protected MessageIDMessage createMessage() {
return new PubRelMessage();
}
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PUBREL, in);
}
}
@@ -0,0 +1,23 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PubRelMessage;
/**
*
* @author andrea
*/
public class PubRelEncoder implements MessageEncoder<PubRelMessage> {
public void encode(IoSession session, PubRelMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.PUBREL << 4));
buff.put(Utils.encodeRemainingLength(2));
Utils.writeWord(buff, message.getMessageID());
buff.flip();
out.write(buff);
}
}
@@ -0,0 +1,57 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.PublishMessage;
import static org.dna.mqtt.moquette.proto.messages.AbstractMessage.*;
/**
*
* @author andrea
*/
public class PublishDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.PUBLISH, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
int startPos = in.position();
//Common decoding part
PublishMessage message = new PublishMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
int remainingLength = message.getRemainingLength();
//Topic name
String topic = Utils.decodeString(in);
if (topic == null) {
return NEED_DATA;
}
message.setTopicName(topic);
if (message.getQos() == QOSType.LEAST_ONE ||
message.getQos() == QOSType.EXACTLY_ONCE) {
message.setMessageID(Utils.readWord(in));
}
int stopPos = in.position();
//read the payload
int payloadSize = remainingLength - (stopPos - startPos - 2);
byte[] b = new byte[payloadSize];
if (in.remaining() < payloadSize) {
return NEED_DATA;
}
in.get(b);
message.setPayload(b);
out.write(message);
return OK;
}
}
@@ -0,0 +1,48 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.PublishMessage;
/**
*
* @author andrea
*/
public class PublishEncoder implements MessageEncoder<PublishMessage> {
public void encode(IoSession session, PublishMessage message, ProtocolEncoderOutput out) throws Exception {
if (message.getQos() == QOSType.RESERVED) {
throw new IllegalArgumentException("Found a message with RESERVED Qos");
}
if (message.getTopicName() == null || message.getTopicName().isEmpty()) {
throw new IllegalArgumentException("Found a message with empty or null topic name");
}
IoBuffer variableHeaderBuff = IoBuffer.allocate(2).setAutoExpand(true);
variableHeaderBuff.put(Utils.encodeString(message.getTopicName()));
if (message.getQos() == QOSType.LEAST_ONE ||
message.getQos() == QOSType.EXACTLY_ONCE ) {
if (message.getMessageID() == null) {
throw new IllegalArgumentException("Found a message with QOS 1 or 2 and not MessageID setted");
}
Utils.writeWord(variableHeaderBuff, message.getMessageID());
}
variableHeaderBuff.put(message.getPayload());
variableHeaderBuff.flip();
int variableHeaderSize = variableHeaderBuff.remaining();
byte flags = Utils.encodeFlags(message);
IoBuffer buff = IoBuffer.allocate(4 + variableHeaderSize);
buff.put((byte) (AbstractMessage.PUBLISH << 4 | flags));
buff.put(Utils.encodeRemainingLength(4 + variableHeaderSize));
buff.put(variableHeaderBuff).flip();
out.write(buff);
}
}
@@ -0,0 +1,46 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.SubAckMessage;
/**
* QoS, DUP and RETAIN are NOT used.
*
* @author andrea
*/
public class SubAckDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.SUBACK, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
SubAckMessage message = new SubAckMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
int remainingLength = message.getRemainingLength();
//MessageID
message.setMessageID(Utils.readWord(in));
//Qos array
if (in.remaining() < remainingLength) {
return NEED_DATA;
}
for (int i = 0; i < remainingLength; i++) {
byte qos = in.get();
message.addType(QOSType.values()[qos]);
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,39 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.SubAckMessage;
/**
*
* @author andrea
*/
public class SubAckEncoder implements MessageEncoder<SubAckMessage> {
public void encode(IoSession session, SubAckMessage message, ProtocolEncoderOutput out) throws Exception {
if (message.types().isEmpty()) {
throw new IllegalArgumentException("Found a suback message with empty topics");
}
IoBuffer variableHeaderBuff = IoBuffer.allocate(4).setAutoExpand(true);
Utils.writeWord(variableHeaderBuff, message.getMessageID());
for (AbstractMessage.QOSType c : message.types()) {
byte ord = (byte) c.ordinal();
variableHeaderBuff.put((byte)c.ordinal());
}
variableHeaderBuff.flip();
int variableHeaderSize = variableHeaderBuff.remaining();
IoBuffer buff = IoBuffer.allocate(4 + variableHeaderSize);
buff.put((byte) (AbstractMessage.SUBACK << 4 ));
buff.put(Utils.encodeRemainingLength(variableHeaderSize));
buff.put(variableHeaderBuff).flip();
out.write(buff);
}
}
@@ -0,0 +1,56 @@
package org.dna.mqtt.moquette.proto;
import java.io.UnsupportedEncodingException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
/**
*
* @author andrea
*/
public class SubscribeDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.SUBSCRIBE, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
SubscribeMessage message = new SubscribeMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
//check qos level
if (message.getQos() != QOSType.LEAST_ONE) {
return NOT_OK;
}
int start = in.position();
//read messageIDs
message.setMessageID(Utils.readWord(in));
int readed = in.position() - start;
while (readed < message.getRemainingLength()) {
decodeSubscription(in, message);
readed = in.position() - start;
}
out.write(message);
return OK;
}
/**
* Populate the message with couple of Qos, topic
*/
private void decodeSubscription(IoBuffer in, SubscribeMessage message) throws UnsupportedEncodingException {
String topic = Utils.decodeString(in);
byte qos = (byte)(in.get() & 0x03);
message.addSubscription(new SubscribeMessage.Couple(qos, topic));
}
}
@@ -0,0 +1,45 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage.Couple;
/**
*
* @author andrea
*/
public class SubscribeEncoder implements MessageEncoder<SubscribeMessage> {
public void encode(IoSession session, SubscribeMessage message, ProtocolEncoderOutput out) throws Exception {
if (message.subscriptions().isEmpty()) {
throw new IllegalArgumentException("Found a subscribe message with empty topics");
}
if (message.getQos() != QOSType.LEAST_ONE) {
throw new IllegalArgumentException("Expected a message with QOS 1, found " + message.getQos());
}
IoBuffer variableHeaderBuff = IoBuffer.allocate(4).setAutoExpand(true);
Utils.writeWord(variableHeaderBuff, message.getMessageID());
for (Couple c : message.subscriptions()) {
variableHeaderBuff.put(Utils.encodeString(c.getTopic()));
variableHeaderBuff.put(c.getQos());
}
variableHeaderBuff.flip();
int variableHeaderSize = variableHeaderBuff.remaining();
byte flags = Utils.encodeFlags(message);
IoBuffer buff = IoBuffer.allocate(4 + variableHeaderSize);
buff.put((byte) (AbstractMessage.SUBSCRIBE << 4 | flags));
buff.put(Utils.encodeRemainingLength(4 + variableHeaderSize));
buff.put(variableHeaderBuff).flip();
out.write(buff);
}
}
@@ -0,0 +1,26 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.MessageIDMessage;
import org.dna.mqtt.moquette.proto.messages.UnsubAckMessage;
/**
*
* @author andrea
*/
public class UnsubAckDecoder extends MessageIDDecoder {
@Override
protected MessageIDMessage createMessage() {
return new UnsubAckMessage();
}
@Override
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.UNSUBACK, in);
}
}
@@ -0,0 +1,23 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.UnsubAckMessage;
/**
*
* @author andrea
*/
public class UnsubAckEncoder implements MessageEncoder<UnsubAckMessage> {
public void encode(IoSession session, UnsubAckMessage message, ProtocolEncoderOutput out) throws Exception {
IoBuffer buff = IoBuffer.allocate(4);
buff.put((byte) (AbstractMessage.UNSUBACK << 4));
buff.put(Utils.encodeRemainingLength(2));
Utils.writeWord(buff, message.getMessageID());
buff.flip();
out.write(buff);
}
}
@@ -0,0 +1,48 @@
package org.dna.mqtt.moquette.proto;
import java.io.UnsupportedEncodingException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
import org.dna.mqtt.moquette.proto.messages.UnsubscribeMessage;
/**
*
* @author andrea
*/
public class UnsubscribeDecoder extends MqttDecoder {
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
return Utils.checkDecodable(AbstractMessage.UNSUBSCRIBE, in);
}
public MessageDecoderResult decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
//Common decoding part
UnsubscribeMessage message = new UnsubscribeMessage();
if (decodeCommonHeader(message, in) == NEED_DATA) {
return NEED_DATA;
}
//check qos level
if (message.getQos() != QOSType.LEAST_ONE) {
return NOT_OK;
}
int start = in.position();
//read messageIDs
message.setMessageID(Utils.readWord(in));
int readed = in.position() - start;
while (readed < message.getRemainingLength()) {
message.addTopic(Utils.decodeString(in));
readed = in.position() - start;
}
out.write(message);
return OK;
}
}
@@ -0,0 +1,43 @@
package org.dna.mqtt.moquette.proto;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.UnsubscribeMessage;
/**
*
* @author andrea
*/
public class UnsubscribeEncoder implements MessageEncoder<UnsubscribeMessage> {
public void encode(IoSession session, UnsubscribeMessage message, ProtocolEncoderOutput out) throws Exception {
if (message.topics().isEmpty()) {
throw new IllegalArgumentException("Found an unsubscribe message with empty topics");
}
if (message.getQos() != QOSType.LEAST_ONE) {
throw new IllegalArgumentException("Expected a message with QOS 1, found " + message.getQos());
}
IoBuffer variableHeaderBuff = IoBuffer.allocate(4).setAutoExpand(true);
Utils.writeWord(variableHeaderBuff, message.getMessageID());
for (String topic : message.topics()) {
variableHeaderBuff.put(Utils.encodeString(topic));
}
variableHeaderBuff.flip();
int variableHeaderSize = variableHeaderBuff.remaining();
byte flags = Utils.encodeFlags(message);
IoBuffer buff = IoBuffer.allocate(4 + variableHeaderSize);
buff.put((byte) (AbstractMessage.UNSUBSCRIBE << 4 | flags));
buff.put(Utils.encodeRemainingLength(4 + variableHeaderSize));
buff.put(variableHeaderBuff).flip();
out.write(buff);
}
}
@@ -0,0 +1,144 @@
package org.dna.mqtt.moquette.proto;
import java.io.UnsupportedEncodingException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
/**
* Common utils methodd used in codecs.
*
* @author andrea
*/
public class Utils {
public static final int MAX_LENGTH_LIMIT = 268435455;
/**
* Read 2 bytes from in buffer first MSB, and then LSB returning as int.
*/
static int readWord(IoBuffer in) {
int msb = in.get() & 0x00FF; //remove sign extension due to casting
int lsb = in.get() & 0x00FF;
msb = (msb << 8) | lsb ;
return msb;
}
/**
* Writes as 2 bytes the int value into buffer first MSB, and then LSB.
*/
static void writeWord(IoBuffer out, int value) {
out.put((byte) ((value & 0xFF00) >> 8)); //msb
out.put((byte) (value & 0x00FF)); //lsb
}
/**
* Decode the variable remaining lenght as defined in MQTT v3.1 specification
* (section 2.1).
*
* @return the decoded length or -1 if needed more data to decode.
*/
static int decodeRemainingLenght(IoBuffer in) {
int multiplier = 1;
int value = 0;
byte digit;
do {
if (in.remaining() < 1) {
return -1;
}
digit = in.get();
value += (digit & 0x7F) * multiplier;
multiplier *= 128;
} while ((digit & 0x80) != 0);
return value;
}
/**
* Encode the value in the format defined in specification as variable length
* array.
*
* @throws IllegalArgumentException if the value is not in the specification bounds
* [0..268435455].
*/
static IoBuffer encodeRemainingLength(int value) throws IllegalAccessException {
if (value > MAX_LENGTH_LIMIT || value < 0) {
throw new IllegalAccessException("Value should in range 0.." + MAX_LENGTH_LIMIT + " found " + value);
}
IoBuffer encoded = IoBuffer.allocate(4);
byte digit;
do {
digit = (byte) (value % 128);
value = value / 128;
// if there are more digits to encode, set the top bit of this digit
if (value > 0) {
digit = (byte) (digit | 0x80);
}
encoded.put(digit);
} while (value > 0);
encoded.flip();
return encoded;
}
static MessageDecoderResult checkDecodable(byte type, IoBuffer in) {
if (in.remaining() < 1) {
return MessageDecoderResult.NEED_DATA;
}
byte h1 = in.get();
byte messageType = (byte) ((h1 & 0x00F0) >> 4);
return messageType == type ? MessageDecoderResult.OK : MessageDecoderResult.NOT_OK;
}
/**
* Return the IoBuffer with string ancoded as MSB, LSB and UTF-8 encoded
* string content.
*/
static IoBuffer encodeString(String str) {
IoBuffer out = IoBuffer.allocate(2).setAutoExpand(true);
byte[] raw;
try {
raw = str.getBytes("UTF-8");
//NB every Java platform has got UTF-8 encoding by default, so this
//exception are never raised.
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(ConnectEncoder.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
Utils.writeWord(out, raw.length);
out.put(raw).flip();
return out;
}
/**
* Load a string from the given buffer, reading first the two bytes of len
* and then the UTF-8 bytes of the string.
*
* @return the decoded string or null if NEED_DATA
*/
static String decodeString(IoBuffer in) throws UnsupportedEncodingException {
if (in.remaining() < 2) {
return null;
}
int strLen = Utils.readWord(in);
if (in.remaining() < strLen) {
return null;
}
byte[] strRaw = new byte[strLen];
in.get(strRaw);
return new String(strRaw, "UTF-8");
}
static byte encodeFlags(AbstractMessage message) {
byte flags = 0;
if (message.isDupFlag()) {
flags |= 0x08;
}
flags |= ((message.getQos().ordinal() & 0x03) << 1);
return flags;
}
}
@@ -0,0 +1,81 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Basic abstract message for all MQTT protocol messages.
*
* @author andrea
*/
public abstract class AbstractMessage {
public static final byte CONNECT = 1; // Client request to connect to Server
public static final byte CONNACK = 2; // Connect Acknowledgment
public static final byte PUBLISH = 3; // Publish message
public static final byte PUBACK = 4; // Publish Acknowledgment
public static final byte PUBREC = 5; //Publish Received (assured delivery part 1)
public static final byte PUBREL = 6; // Publish Release (assured delivery part 2)
public static final byte PUBCOMP = 7; //Publish Complete (assured delivery part 3)
public static final byte SUBSCRIBE = 8; //Client Subscribe request
public static final byte SUBACK = 9; // Subscribe Acknowledgment
public static final byte UNSUBSCRIBE = 10; //Client Unsubscribe request
public static final byte UNSUBACK = 11; // Unsubscribe Acknowledgment
public static final byte PINGREQ = 12; //PING Request
public static final byte PINGRESP = 13; //PING Response
public static final byte DISCONNECT = 14; //Client is Disconnecting
public static enum QOSType {
MOST_ONE, LEAST_ONE, EXACTLY_ONCE, RESERVED;
}
//type
protected boolean m_dupFlag;
protected QOSType m_qos;
protected boolean m_retainFlag;
protected int m_remainingLength;
protected byte m_messageType;
public byte getMessageType() {
return m_messageType;
}
public void setMessageType(byte messageType) {
this.m_messageType = messageType;
}
public boolean isDupFlag() {
return m_dupFlag;
}
public void setDupFlag(boolean dupFlag) {
this.m_dupFlag = dupFlag;
}
public QOSType getQos() {
return m_qos;
}
public void setQos(QOSType qos) {
this.m_qos = qos;
}
public boolean isRetainFlag() {
return m_retainFlag;
}
public void setRetainFlag(boolean retainFlag) {
this.m_retainFlag = retainFlag;
}
/**
* TOBE used only internally
*/
public int getRemainingLength() {
return m_remainingLength;
}
/**
* TOBE used only internally
*/
public void setRemainingLength(int remainingLength) {
this.m_remainingLength = remainingLength;
}
}
@@ -0,0 +1,26 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* The attributes Qos, Dup and Retain aren't used.
*
* @author andrea
*/
public class ConnAckMessage extends AbstractMessage {
public static final byte CONNECTION_ACCEPTED = 0x00;
public static final byte UNNACEPTABLE_PROTOCOL_VERSION = 0x01;
public static final byte IDENTIFIER_REJECTED = 0x02;
public static final byte SERVER_UNAVAILABLE = 0x03;
public static final byte BAD_USERNAME_OR_PASSWORD = 0x04;
public static final byte NOT_AUTHORIZED = 0x05;
private byte m_returnCode;
public byte getReturnCode() {
return m_returnCode;
}
public void setReturnCode(byte returnCode) {
this.m_returnCode = returnCode;
}
}
@@ -0,0 +1,140 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* The attributes Qos, Dup and Retain aren't used for Connect message
*
* @author andrea
*/
public class ConnectMessage extends AbstractMessage {
String m_protocolName;
byte m_procotolVersion;
//Connection flags
boolean m_cleanSession;
boolean m_willFlag;
byte m_willQos;
boolean m_willRetain;
boolean m_passwordFlag;
boolean m_userFlag;
int m_keepAlive;
//Variable part
String m_username;
String m_password;
String m_clientID;
String m_willtopic;
String m_willMessage;
public boolean isCleanSession() {
return m_cleanSession;
}
public void setCleanSession(boolean cleanSession) {
this.m_cleanSession = cleanSession;
}
public int getKeepAlive() {
return m_keepAlive;
}
public void setKeepAlive(int keepAlive) {
this.m_keepAlive = keepAlive;
}
public boolean isPasswordFlag() {
return m_passwordFlag;
}
public void setPasswordFlag(boolean passwordFlag) {
this.m_passwordFlag = passwordFlag;
}
public byte getProcotolVersion() {
return m_procotolVersion;
}
public void setProcotolVersion(byte procotolVersion) {
this.m_procotolVersion = procotolVersion;
}
public String getProtocolName() {
return m_protocolName;
}
public void setProtocolName(String protocolName) {
this.m_protocolName = protocolName;
}
public boolean isUserFlag() {
return m_userFlag;
}
public void setUserFlag(boolean userFlag) {
this.m_userFlag = userFlag;
}
public boolean isWillFlag() {
return m_willFlag;
}
public void setWillFlag(boolean willFlag) {
this.m_willFlag = willFlag;
}
public byte getWillQos() {
return m_willQos;
}
public void setWillQos(byte willQos) {
this.m_willQos = willQos;
}
public boolean isWillRetain() {
return m_willRetain;
}
public void setWillRetain(boolean willRetain) {
this.m_willRetain = willRetain;
}
public String getPassword() {
return m_password;
}
public void setPassword(String password) {
this.m_password = password;
}
public String getUsername() {
return m_username;
}
public void setUsername(String username) {
this.m_username = username;
}
public String getClientID() {
return m_clientID;
}
public void setClientID(String clientID) {
this.m_clientID = clientID;
}
public String getWillTopic() {
return m_willtopic;
}
public void setWillTopic(String topic) {
this.m_willtopic = topic;
}
public String getWillMessage() {
return m_willMessage;
}
public void setWillMessage(String willMessage) {
this.m_willMessage = willMessage;
}
}
@@ -0,0 +1,9 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Doesn't care DUP, QOS and RETAIN flags.
*
* @author andrea
*/
public class DisconnectMessage extends ZeroLengthMessage {
}
@@ -0,0 +1,22 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Base class for alla the messages that carries only MessageID. (PUBACK, PUBREC,
* PUBREL, PUBCOMP, UNSUBACK)
*
* The flags dup, QOS and Retained doens't take care.
*
* @author andrea
*/
public abstract class MessageIDMessage extends AbstractMessage {
int m_messageID;
public int getMessageID() {
return m_messageID;
}
public void setMessageID(int messageID) {
this.m_messageID = messageID;
}
}
@@ -0,0 +1,9 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Doesn't care DUP, QOS and RETAIN flags.
*
* @author andrea
*/
public class PingReqMessage extends ZeroLengthMessage {
}
@@ -0,0 +1,8 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Doesn't care DUP, QOS and RETAIN flags.
* @author andrea
*/
public class PingRespMessage extends ZeroLengthMessage {
}
@@ -0,0 +1,9 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Placeholder for PUBACK message.
*
* @author andrea
*/
public class PubAckMessage extends MessageIDMessage {
}
@@ -0,0 +1,10 @@
package org.dna.mqtt.moquette.proto.messages;
/**
* Placeholder for PUBREC message.
*
* @author andrea
*/
public class PubRecMessage extends MessageIDMessage {
}
@@ -0,0 +1,8 @@
package org.dna.mqtt.moquette.proto.messages;
/**
*
* @author andrea
*/
public class PubRelMessage extends MessageIDMessage {
}
@@ -0,0 +1,36 @@
package org.dna.mqtt.moquette.proto.messages;
/**
*
* @author andrea
*/
public class PublishMessage extends AbstractMessage {
private String m_topicName;
private Integer m_messageID; //could be null if Qos is == 0
private byte[] m_payload;
public Integer getMessageID() {
return m_messageID;
}
public void setMessageID(Integer messageID) {
this.m_messageID = messageID;
}
public String getTopicName() {
return m_topicName;
}
public void setTopicName(String topicName) {
this.m_topicName = topicName;
}
public byte[] getPayload() {
return m_payload;
}
public void setPayload(byte[] payload) {
this.m_payload = payload;
}
}
@@ -0,0 +1,21 @@
package org.dna.mqtt.moquette.proto.messages;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author andrea
*/
public class SubAckMessage extends MessageIDMessage {
List<QOSType> m_types = new ArrayList<QOSType>();
public List<QOSType> types() {
return m_types;
}
public void addType(QOSType type) {
m_types.add(type);
}
}
@@ -0,0 +1,39 @@
package org.dna.mqtt.moquette.proto.messages;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author andrea
*/
public class SubscribeMessage extends MessageIDMessage {
public static class Couple {
private byte m_qos;
private String m_topic;
public Couple(byte qos, String topic) {
m_qos = qos;
m_topic = topic;
}
public byte getQos() {
return m_qos;
}
public String getTopic() {
return m_topic;
}
}
private List<Couple> m_subscriptions = new ArrayList<Couple>();
public List<Couple> subscriptions() {
return m_subscriptions;
}
public void addSubscription(Couple subscription) {
m_subscriptions.add(subscription);
}
}
@@ -0,0 +1,9 @@
package org.dna.mqtt.moquette.proto.messages;
/**
*
* @author andrea
*/
public class UnsubAckMessage extends MessageIDMessage {
}
@@ -0,0 +1,20 @@
package org.dna.mqtt.moquette.proto.messages;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author andrea
*/
public class UnsubscribeMessage extends MessageIDMessage {
List<String> m_types = new ArrayList<String>();
public List<String> topics() {
return m_types;
}
public void addTopic(String type) {
m_types.add(type);
}
}
@@ -0,0 +1,9 @@
package org.dna.mqtt.moquette.proto.messages;
/**
*
* @author andrea
*/
public abstract class ZeroLengthMessage extends AbstractMessage {
}
@@ -0,0 +1,11 @@
package org.dna.mqtt.moquette.server;
/**
* username and password checker
*
* @author andrea
*/
public interface IAuthenticator {
boolean checkValid(String username, String password);
}
@@ -0,0 +1,102 @@
package org.dna.mqtt.moquette.server;
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
import static org.dna.mqtt.moquette.proto.messages.AbstractMessage.*;
/**
*
* @author andrea
*/
public class MQTTHandler extends IoHandlerAdapter {
private static final Logger LOG = Logger.getLogger(MQTTHandler.class.getName());
Map<String, IoSession> m_clientIDs = new HashMap<String, IoSession>();
private IMessaging m_messaging;
private IAuthenticator m_authenticator;
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
AbstractMessage msg = (AbstractMessage) message;
LOG.fine("Received a message of type " + msg.getMessageType());
switch (msg.getMessageType()) {
case CONNECT:
handleConnect(session, (ConnectMessage) msg);
}
}
protected void handleConnect(IoSession session, ConnectMessage msg) {
if (msg.getProcotolVersion() != 0x03) {
ConnAckMessage badProto = new ConnAckMessage();
badProto.setReturnCode(ConnAckMessage.UNNACEPTABLE_PROTOCOL_VERSION);
session.write(badProto);
session.close(false);
return;
}
if (msg.getClientID().length() > 23) {
ConnAckMessage okResp = new ConnAckMessage();
okResp.setReturnCode(ConnAckMessage.IDENTIFIER_REJECTED);
session.write(okResp);
return;
}
//if an old client with the same ID already exists close its session.
if (m_clientIDs.containsKey(msg.getClientID())) {
m_clientIDs.get(msg.getClientID()).close(false);
}
m_clientIDs.put(msg.getClientID(), session);
int keepAlive = msg.getKeepAlive();
session.setAttribute("keepAlive", keepAlive);
session.setAttribute("cleanSession", msg.isCleanSession());
session.getConfig().setIdleTime(IdleStatus.READER_IDLE, Math.round(keepAlive * 1.5f));
//Handle will flag
if (msg.isWillFlag()) {
m_messaging.publish(msg.getWillTopic(), msg.getWillMessage(), msg.getWillQos(), msg.isWillRetain());
}
//handle user authentication
if (msg.isUserFlag()) {
String pwd = null;
if (msg.isPasswordFlag()) {
pwd = msg.getPassword();
}
if (!m_authenticator.checkValid(msg.getUsername(), pwd)) {
ConnAckMessage okResp = new ConnAckMessage();
okResp.setReturnCode(ConnAckMessage.BAD_USERNAME_OR_PASSWORD);
session.write(okResp);
return;
}
}
//TODO handle clean session flag
ConnAckMessage okResp = new ConnAckMessage();
okResp.setReturnCode(ConnAckMessage.CONNECTION_ACCEPTED);
session.write(okResp);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) {
session.close(false);
}
public void setMessaging(IMessaging messaging) {
m_messaging = messaging;
}
public void setAuthenticator(IAuthenticator authenticator) {
m_authenticator = authenticator;
}
}
@@ -0,0 +1,47 @@
package org.dna.mqtt.moquette.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.demux.DemuxingProtocolDecoder;
import org.apache.mina.filter.codec.demux.DemuxingProtocolEncoder;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.dna.mqtt.moquette.proto.ConnAckEncoder;
import org.dna.mqtt.moquette.proto.ConnectDecoder;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
/**
* Launch a configured version of the server.
* @author andrea
*/
public class Server {
private static final Logger LOG = Logger.getLogger(Server.class.getName());
public static final int PORT = 9191;
public static final int DEFAULT_CONNECT_TIMEOUT = 10;
public static void main(String[] args) throws IOException {
DemuxingProtocolDecoder decoder = new DemuxingProtocolDecoder();
decoder.addMessageDecoder(new ConnectDecoder());
DemuxingProtocolEncoder encoder = new DemuxingProtocolEncoder();
// encoder.addMessageEncoder(ConnectMessage.class, new ConnectEncoder());
encoder.addMessageEncoder(ConnAckMessage.class, new ConnAckEncoder());
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(encoder, decoder));
acceptor.setHandler( new MQTTHandler() );
acceptor.getSessionConfig().setReadBufferSize( 2048 );
acceptor.getSessionConfig().setIdleTime( IdleStatus.BOTH_IDLE, DEFAULT_CONNECT_TIMEOUT );
acceptor.bind( new InetSocketAddress(PORT) );
LOG.info("Server binded");
}
}
@@ -0,0 +1,63 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.ConnAckDecoder;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class ConnAckDecoderTest {
IoBuffer m_buff;
ConnAckDecoder m_msgdec;
MockProtocolDecoderOutput<ConnAckMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new ConnAckDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<ConnAckMessage>();
}
@Test
public void testDecodable_OK() {
m_buff.put((byte)(AbstractMessage.CONNACK << 4)).flip();
assertEquals(MessageDecoder.OK , m_msgdec.decodable(null, m_buff));
}
@Test
public void testDecodable_NOT_OK() {
m_buff.put((byte)(AbstractMessage.CONNECT << 4)).flip();
assertEquals(MessageDecoder.NOT_OK , m_msgdec.decodable(null, m_buff));
}
@Test
public void testHeader() throws Exception {
m_buff = IoBuffer.allocate(14);
initHeader(m_buff);
m_buff.flip();
//Exercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNotNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.OK, res);
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_mockProtoDecoder.getMessage().getReturnCode());
}
private void initHeader(IoBuffer buff) {
buff.clear().put((byte)(AbstractMessage.CONNACK << 4)).put((byte)2);
//reserved
buff.put((byte)0);
//return code
buff.put(ConnAckMessage.CONNECTION_ACCEPTED);
}
}
@@ -0,0 +1,28 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.ConnAckEncoder;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class ConnAckEncoderTest {
@Test
public void testHeaderEncode() throws Exception {
TestUtils.MockProtocolEncoderOutput mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
ConnAckEncoder encoder = new ConnAckEncoder();
ConnAckMessage msg = new ConnAckMessage();
//Exercise
encoder.encode(null, msg, mockProtoEncoder);
//Verify
assertEquals(0x20, mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(0x02, mockProtoEncoder.getBuffer().get()); //2 byte, length
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, mockProtoEncoder.getBuffer().skip(1).get());
}
}
@@ -0,0 +1,188 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.ConnectDecoder;
import org.dna.mqtt.moquette.proto.Utils;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import java.io.UnsupportedEncodingException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class ConnectDecoderTest {
IoBuffer m_buff;
ConnectDecoder m_msgdec;
MockProtocolDecoderOutput<ConnectMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new ConnectDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<ConnectMessage>();
}
@Test
public void testDecodable_OK() {
m_buff.put((byte)(AbstractMessage.CONNECT << 4)).flip();
assertEquals(MessageDecoder.OK , m_msgdec.decodable(null, m_buff));
}
@Test
public void testDecodable_NOT_OK() {
m_buff.put((byte)(AbstractMessage.CONNACK << 4)).flip();
assertEquals(MessageDecoder.NOT_OK , m_msgdec.decodable(null, m_buff));
}
@Test
public void testDecodable_NEED_DATA() {
m_buff = IoBuffer.allocate(0);
assertFalse(m_buff.hasRemaining());
assertEquals(MessageDecoder.NEED_DATA , m_msgdec.decodable(null, m_buff));
}
@Test
public void testDecodeRemainingLenght() {
//1 byte length
m_buff.put((byte)0x0).flip();
assertEquals(0, Utils.decodeRemainingLenght(m_buff));
m_buff.clear().put((byte) 0x007F).flip();
assertEquals(127, Utils.decodeRemainingLenght(m_buff));
//2 byte length
m_buff.clear().put((byte) 0x80).put((byte) 0x01).flip();
assertEquals(128, Utils.decodeRemainingLenght(m_buff));
m_buff.clear().put((byte) 0xFF).put((byte) 0x7F).flip();
assertEquals(16383, Utils.decodeRemainingLenght(m_buff));
//3 byte length
m_buff.clear().put((byte) 0x80).put((byte) 0x80).put((byte)0x01).flip();
assertEquals(16384, Utils.decodeRemainingLenght(m_buff));
m_buff.clear().put((byte) 0xFF).put((byte) 0xFF).put((byte)0x7F).flip();
assertEquals(2097151, Utils.decodeRemainingLenght(m_buff));
//4 byte length
m_buff.clear().put((byte) 0x80).put((byte) 0x80).put((byte)0x80).put((byte)0x01).flip();
assertEquals(2097152, Utils.decodeRemainingLenght(m_buff));
m_buff.clear().put((byte) 0xFF).put((byte) 0xFF).put((byte) 0xFF).put((byte)0x7F).flip();
assertEquals(268435455, Utils.decodeRemainingLenght(m_buff));
}
@Test
public void testBaseHeader() throws Exception {
m_buff = IoBuffer.allocate(14);
initBaseHeader(m_buff);
m_buff.flip();
//Excercise
m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
verifyBaseHeader(m_mockProtoDecoder.getMessage());
}
@Test
public void testBaseHeader_ClientID() throws UnsupportedEncodingException, Exception {
m_buff = IoBuffer.allocate(40);
initHeader(m_buff, (byte) 38);
encodeString(m_buff, "ABCDEFGH");
encodeString(m_buff, "Topic");
encodeString(m_buff, "Message");
m_buff.flip();
//Excercise
m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
verifyBaseHeader(m_mockProtoDecoder.getMessage());
assertEquals("ABCDEFGH", m_mockProtoDecoder.getMessage().getClientID());
assertEquals("Topic", m_mockProtoDecoder.getMessage().getWillTopic());
assertEquals("Message", m_mockProtoDecoder.getMessage().getWillMessage());
}
@Test
public void testBaseHeader_extra_with_user_pwd() throws UnsupportedEncodingException, Exception {
m_buff = IoBuffer.allocate(55);
initHeader(m_buff, (byte) 53);
encodeString(m_buff, "ABCDEFGH");
encodeString(m_buff, "Topic");
encodeString(m_buff, "Message");
encodeString(m_buff, "Fakeuser");
encodeString(m_buff, "pwd");
m_buff.flip();
//Excercise
m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
verifyBaseHeader(m_mockProtoDecoder.getMessage());
assertEquals("ABCDEFGH", m_mockProtoDecoder.getMessage().getClientID());
assertEquals("Topic", m_mockProtoDecoder.getMessage().getWillTopic());
assertEquals("Message", m_mockProtoDecoder.getMessage().getWillMessage());
assertEquals("Fakeuser", m_mockProtoDecoder.getMessage().getUsername());
assertEquals("pwd", m_mockProtoDecoder.getMessage().getPassword());
}
@Test
public void testBadFlagUserPwd() throws UnsupportedEncodingException, Exception {
m_buff = IoBuffer.allocate(14);
m_buff.clear().put((byte)(AbstractMessage.CONNECT << 4)).put((byte)12);
//Proto name
encodeString(m_buff, "MQIsdp");
//version
m_buff.put((byte)3);
//conn flags
m_buff.put((byte)0x4E); //sets user to false and password to true
//keepAlive
m_buff.put((byte)0).put((byte) 0x0A);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.NOT_OK, res);
}
/**
* Encode and insert the given stirng into the given buff
*/
private IoBuffer encodeString(IoBuffer buff, String str) throws UnsupportedEncodingException {
buff.put(Utils.encodeString(str));
return buff;
}
private void initBaseHeader(IoBuffer buff) throws UnsupportedEncodingException {
initHeader(buff, (byte)0x0C);
}
private void initHeader(IoBuffer buff, byte remaingLength) throws UnsupportedEncodingException {
buff.clear().put((byte)(AbstractMessage.CONNECT << 4)).put(remaingLength);
//Proto name
encodeString(buff, "MQIsdp");
//version
buff.put((byte)3);
//conn flags
buff.put((byte)0xCE);
//keepAlive
buff.put((byte)0).put((byte) 0x0A);
}
private void verifyBaseHeader(ConnectMessage connMessage) {
assertNotNull(connMessage);
assertEquals("MQIsdp", connMessage.getProtocolName());
assertEquals(3, connMessage.getProcotolVersion());
assertTrue(connMessage.isUserFlag());
assertTrue(connMessage.isPasswordFlag());
assertTrue(connMessage.isCleanSession());
assertEquals(10, connMessage.getKeepAlive());
assertTrue(connMessage.isWillFlag());
assertFalse(connMessage.isWillRetain());
assertEquals(1, connMessage.getWillQos());
}
}
@@ -0,0 +1,147 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.ConnectEncoder;
import org.dna.mqtt.moquette.proto.Utils;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.dna.mqtt.moquette.proto.TestUtils.*;
/**
*
* @author andrea
*/
public class ConnectEncoderTest {
ConnectEncoder m_encoder;
TestUtils.MockProtocolEncoderOutput m_mockProtoEncoder;
@Before
public void setUp() {
m_encoder = new ConnectEncoder();
m_mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
}
@Test(expected=IllegalAccessException.class)
public void testEncodeRemainingLength_invalid_upper() throws IllegalAccessException {
Utils.encodeRemainingLength(Utils.MAX_LENGTH_LIMIT + 1);
}
@Test(expected=IllegalAccessException.class)
public void testEncodeRemainingLength_invalid_lower() throws IllegalAccessException {
Utils.encodeRemainingLength(-1);
}
@Test
public void testEncodeRemainingLenght() throws IllegalAccessException {
//1 byte length
verifyBuff(1, new byte[]{0}, Utils.encodeRemainingLength(0));
verifyBuff(1, new byte[]{0x7F}, Utils.encodeRemainingLength(127));
//2 byte length
verifyBuff(2, new byte[]{(byte)0x80, 0x01}, Utils.encodeRemainingLength(128));
verifyBuff(2, new byte[]{(byte)0xFF, 0x7F}, Utils.encodeRemainingLength(16383));
//3 byte length
verifyBuff(3, new byte[]{(byte)0x80, (byte)0x80, 0x01}, Utils.encodeRemainingLength(16384));
verifyBuff(3, new byte[]{(byte)0xFF, (byte)0xFF, 0x7F}, Utils.encodeRemainingLength(2097151));
//4 byte length
verifyBuff(4, new byte[]{(byte)0x80, (byte)0x80, (byte)0x80, 0x01}, Utils.encodeRemainingLength(2097152));
verifyBuff(4, new byte[]{(byte)0xFF, (byte)0xFF, (byte)0xFF, 0x7F}, Utils.encodeRemainingLength(268435455));
}
@Test
public void testBaseHeader() throws Exception {
ConnectMessage msg = new ConnectMessage();
msg.setWillRetain(true);
msg.setWillQos((byte)2);
msg.setWillFlag(false);
msg.setCleanSession(true);
msg.setKeepAlive(512);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals(0x10, m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(12, m_mockProtoEncoder.getBuffer().get()); //remaining length
verifyString("MQIsdp", m_mockProtoEncoder.getBuffer());
assertEquals(0x03, m_mockProtoEncoder.getBuffer().get()); //protocol version
assertEquals(0x32, m_mockProtoEncoder.getBuffer().get()); //flags
assertEquals(2, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer msb
assertEquals(0, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer lsb
}
@Test
public void testCompleteHeader() throws Exception {
ConnectMessage msg = new ConnectMessage();
msg.setWillRetain(true);
msg.setWillQos((byte)2);
msg.setWillFlag(true);
msg.setCleanSession(true);
msg.setKeepAlive(512);
//variable part
msg.setClientID("ABCDEF");
msg.setWillTopic("Topic");
msg.setWillMessage("Message");
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals(0x10, m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(36, m_mockProtoEncoder.getBuffer().get()); //remaining length
verifyString("MQIsdp", m_mockProtoEncoder.getBuffer());
assertEquals(0x03, m_mockProtoEncoder.getBuffer().get()); //protocol version
assertEquals(0x36, m_mockProtoEncoder.getBuffer().get()); //flags
assertEquals(2, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer msb
assertEquals(0, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer lsb
//Variable part
verifyString("ABCDEF", m_mockProtoEncoder.getBuffer());
verifyString("Topic", m_mockProtoEncoder.getBuffer());
verifyString("Message", m_mockProtoEncoder.getBuffer());
}
@Test
public void testCompleteHeaderWIthUser_password() throws Exception {
ConnectMessage msg = new ConnectMessage();
msg.setUserFlag(true);
msg.setPasswordFlag(true);
msg.setWillRetain(true);
msg.setWillQos((byte)2);
msg.setWillFlag(true);
msg.setCleanSession(true);
msg.setKeepAlive(512);
//variable part
msg.setClientID("ABCDEF");
msg.setWillTopic("Topic");
msg.setWillMessage("Message");
msg.setUsername("Pablo");
msg.setPassword("PBL");
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals(0x10, m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(48, m_mockProtoEncoder.getBuffer().get()); //remaining length
verifyString("MQIsdp", m_mockProtoEncoder.getBuffer());
assertEquals(0x03, m_mockProtoEncoder.getBuffer().get()); //protocol version
assertEquals((byte)0xF6, (byte)m_mockProtoEncoder.getBuffer().get()); //flags
assertEquals(2, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer msb
assertEquals(0, m_mockProtoEncoder.getBuffer().get()); //keepAliveTimer lsb
//Variable part
verifyString("ABCDEF", m_mockProtoEncoder.getBuffer());
verifyString("Topic", m_mockProtoEncoder.getBuffer());
verifyString("Message", m_mockProtoEncoder.getBuffer());
verifyString("Pablo", m_mockProtoEncoder.getBuffer());//username
verifyString("PBL", m_mockProtoEncoder.getBuffer());//password
}
}
@@ -0,0 +1,52 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.PubAckDecoder;
import org.dna.mqtt.moquette.proto.Utils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.PubAckMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class PubAckDecoderTest {
IoBuffer m_buff;
PubAckDecoder m_msgdec;
MockProtocolDecoderOutput<PubAckMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new PubAckDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<PubAckMessage>();
}
@Test
public void testHeader() throws Exception {
m_buff = IoBuffer.allocate(14);
int messageId = 0xAABB;
initHeader(m_buff, messageId);
m_buff.flip();
//Exercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNotNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.OK, res);
assertEquals(messageId, m_mockProtoDecoder.getMessage().getMessageID());
}
private void initHeader(IoBuffer buff, int messageID) {
buff.clear().put((byte)(AbstractMessage.PUBACK << 4)).put((byte)2);
//return code
Utils.writeWord(buff, messageID);
}
}
@@ -0,0 +1,31 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.PubAckEncoder;
import org.dna.mqtt.moquette.proto.messages.PubAckMessage;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class PubAckEncoderTest {
@Test
public void testHeaderEncode() throws Exception {
int messageID = 0xAABB;
TestUtils.MockProtocolEncoderOutput mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
PubAckEncoder encoder = new PubAckEncoder();
PubAckMessage msg = new PubAckMessage();
msg.setMessageID(messageID);
//Exercise
encoder.encode(null, msg, mockProtoEncoder);
//Verify
assertEquals(0x40, mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(0x02, mockProtoEncoder.getBuffer().get()); //2 byte, length
assertEquals((byte)0xAA, mockProtoEncoder.getBuffer().get());
assertEquals((byte)0xBB, mockProtoEncoder.getBuffer().get());
}
}
@@ -0,0 +1,114 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.PublishDecoder;
import org.dna.mqtt.moquette.proto.Utils;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.messages.PublishMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class PublishDecoderTest {
IoBuffer m_buff;
PublishDecoder m_msgdec;
MockProtocolDecoderOutput<PublishMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new PublishDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<PublishMessage>();
}
@Test
public void testDecodable_OK() {
m_buff.put((byte) (AbstractMessage.PUBLISH << 4)).flip();
assertEquals(MessageDecoder.OK, m_msgdec.decodable(null, m_buff));
}
@Test
public void testHeader() throws Exception {
m_buff = IoBuffer.allocate(14);
initHeader(m_buff);
m_buff.flip();
//Exercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNotNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.OK, res);
assertEquals("Fake Topic", m_mockProtoDecoder.getMessage().getTopicName());
assertNull(m_mockProtoDecoder.getMessage().getMessageID());
}
@Test
public void testHeaderWithMessageID() throws Exception {
m_buff = IoBuffer.allocate(14).setAutoExpand(true);
int messageID = 123;
initHeaderWithMessageID(m_buff, messageID);
m_buff.flip();
//Exercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNotNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.OK, res);
assertEquals("Fake Topic", m_mockProtoDecoder.getMessage().getTopicName());
assertEquals(messageID, (int) m_mockProtoDecoder.getMessage().getMessageID());
}
@Test
public void testHeaderWithMessageID_Payload() throws Exception {
m_buff = IoBuffer.allocate(14).setAutoExpand(true);
int messageID = 123;
byte[] payload = new byte[]{0x0A, 0x0B, 0x0C};
initHeaderWithMessageID_Payload(m_buff, messageID, payload);
m_buff.flip();
//Exercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
assertNotNull(m_mockProtoDecoder.getMessage());
assertEquals(MessageDecoder.OK, res);
assertEquals("Fake Topic", m_mockProtoDecoder.getMessage().getTopicName());
assertEquals(messageID, (int) m_mockProtoDecoder.getMessage().getMessageID());
TestUtils.verifyEquals(payload, m_mockProtoDecoder.getMessage().getPayload());
}
private void initHeader(IoBuffer buff) throws IllegalAccessException {
IoBuffer tmp = IoBuffer.allocate(4).setAutoExpand(true).put(Utils.encodeString("Fake Topic")).flip();
buff.clear().put((byte) (AbstractMessage.PUBLISH << 4)).put(Utils.encodeRemainingLength(tmp.remaining()));
//topic name
buff.put(tmp);
}
private void initHeaderWithMessageID(IoBuffer buff, int messageID) throws IllegalAccessException {
IoBuffer tmp = IoBuffer.allocate(4).setAutoExpand(true).put(Utils.encodeString("Fake Topic"));
Utils.writeWord(tmp, messageID);
tmp.flip();
buff.clear().put((byte) (AbstractMessage.PUBLISH << 4 | 0x02)) //set Qos to 1
.put(Utils.encodeRemainingLength(tmp.remaining()));
//topic name
buff.put(tmp);
}
private void initHeaderWithMessageID_Payload(IoBuffer buff, int messageID, byte[] payload) throws IllegalAccessException {
IoBuffer tmp = IoBuffer.allocate(4).setAutoExpand(true).put(Utils.encodeString("Fake Topic"));
Utils.writeWord(tmp, messageID);
tmp.put(payload);
tmp.flip();
buff.clear().put((byte) (AbstractMessage.PUBLISH << 4 | 0x02)) //set Qos to 1
.put(Utils.encodeRemainingLength(tmp.remaining()));
//topic name
buff.put(tmp);
}
}
@@ -0,0 +1,121 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.PublishEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.PublishMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.dna.mqtt.moquette.proto.TestUtils.*;
/**
*
* @author andrea
*/
public class PublishEncoderTest {
PublishEncoder m_encoder;
TestUtils.MockProtocolEncoderOutput m_mockProtoEncoder;
@Before
public void setUp() {
m_encoder = new PublishEncoder();
m_mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
}
@Test
public void testEncodeWithQos_0_noMessageID() throws Exception {
String topic = "pictures/photos";
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.MOST_ONE);
msg.setTopicName(topic);
//variable part
byte[] payload = new byte[]{0x0A, 0x0B, 0x0C};
msg.setPayload(payload);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals(0x30, m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(24, m_mockProtoEncoder.getBuffer().get()); //remaining length
//Variable part
verifyString(topic, m_mockProtoEncoder.getBuffer());
verifyBuff(payload.length, payload, m_mockProtoEncoder.getBuffer());
}
@Test
public void testEncodeWithQos_1_MessageID() throws Exception {
String topic = "pictures/photos";
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setMessageID(1);
msg.setTopicName(topic);
//variable part
byte[] payload = new byte[]{0x0A, 0x0B, 0x0C};
msg.setPayload(payload);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals(0x32, m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(26, m_mockProtoEncoder.getBuffer().get()); //remaining length
//Variable part
verifyString(topic, m_mockProtoEncoder.getBuffer());
assertEquals(0, m_mockProtoEncoder.getBuffer().get()); //MessageID MSB
assertEquals(1, m_mockProtoEncoder.getBuffer().get()); //MessageID LSB
verifyBuff(payload.length, payload, m_mockProtoEncoder.getBuffer());
}
@Test(expected = IllegalArgumentException.class)
public void testEncodeWithQos_1_noMessageID_fail() throws Exception {
String topic = "pictures/photos";
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setTopicName(topic);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_empty_topic() throws Exception {
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setMessageID(1);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_null_topic() throws Exception {
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setMessageID(1);
msg.setTopicName(null);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_qos_reserved() throws Exception {
PublishMessage msg = new PublishMessage();
msg.setQos(QOSType.RESERVED);
msg.setMessageID(1);
msg.setTopicName(null);
//variable part
byte[] payload = new byte[]{0x0A, 0x0B, 0x0C};
msg.setPayload(payload);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
}
@@ -0,0 +1,60 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.SubAckDecoder;
import org.dna.mqtt.moquette.proto.Utils;
import java.util.List;
import org.dna.mqtt.moquette.proto.messages.SubAckMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class SubAckDecoderTest {
IoBuffer m_buff;
SubAckDecoder m_msgdec;
MockProtocolDecoderOutput<SubAckMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(7);
m_msgdec = new SubAckDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<SubAckMessage>();
}
@Test
public void testBadQos() throws Exception {
initHeaderQos(m_buff, 0xAABB, QOSType.LEAST_ONE, QOSType.MOST_ONE, QOSType.MOST_ONE);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
assertEquals(MessageDecoderResult.OK, res);
assertEquals(0xAABB, m_mockProtoDecoder.getMessage().getMessageID());
List<QOSType> qoses = m_mockProtoDecoder.getMessage().types();
assertEquals(3, qoses.size());
assertEquals(QOSType.LEAST_ONE, qoses.get(0));
assertEquals(QOSType.MOST_ONE, qoses.get(1));
assertEquals(QOSType.MOST_ONE, qoses.get(2));
}
private void initHeaderQos(IoBuffer buff, int messageID, QOSType... qoss) throws IllegalAccessException {
buff.clear().put((byte) (AbstractMessage.SUBSCRIBE << 4)).
put(Utils.encodeRemainingLength(3));
Utils.writeWord(buff, messageID);
for (QOSType qos : qoss) {
buff.put((byte)qos.ordinal());
}
}
}
@@ -0,0 +1,58 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.SubAckEncoder;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.dna.mqtt.moquette.proto.messages.SubAckMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class SubAckEncoderTest {
SubAckEncoder m_encoder;
TestUtils.MockProtocolEncoderOutput m_mockProtoEncoder;
@Before
public void setUp() {
m_encoder = new SubAckEncoder();
m_mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
}
@Test(expected = IllegalArgumentException.class)
public void testEncodeWithNoQoss() throws Exception {
SubAckMessage msg = new SubAckMessage();
msg.setMessageID(123);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
@Test
public void testEncodeWithMultipleQos() throws Exception {
SubAckMessage msg = new SubAckMessage();
int messageID = 0xAABB;
msg.setMessageID(messageID);
msg.addType(QOSType.MOST_ONE);
msg.addType(QOSType.LEAST_ONE);
msg.addType(QOSType.EXACTLY_ONCE);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals((byte) (AbstractMessage.SUBACK << 4 ), m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(5, m_mockProtoEncoder.getBuffer().get()); //remaining length
//Variable part
assertEquals((byte)0xAA, m_mockProtoEncoder.getBuffer().get()); //MessageID MSB
assertEquals((byte)0xBB, m_mockProtoEncoder.getBuffer().get()); //MessageID LSB
assertEquals((byte)QOSType.MOST_ONE.ordinal(), m_mockProtoEncoder.getBuffer().get());
assertEquals((byte)QOSType.LEAST_ONE.ordinal(), m_mockProtoEncoder.getBuffer().get());
assertEquals((byte)QOSType.EXACTLY_ONCE.ordinal(), m_mockProtoEncoder.getBuffer().get());
}
}
@@ -0,0 +1,79 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.SubscribeDecoder;
import org.dna.mqtt.moquette.proto.Utils;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage.Couple;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class SubscribeDecoderTest {
IoBuffer m_buff;
SubscribeDecoder m_msgdec;
MockProtocolDecoderOutput<SubscribeMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new SubscribeDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<SubscribeMessage>();
}
@Test
public void testBadQos() throws Exception {
m_buff = IoBuffer.allocate(2);
initHeaderBadQos(m_buff);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
assertEquals(MessageDecoderResult.NOT_OK, res);
}
@Test
public void testMultiTopic() throws Exception {
m_buff = IoBuffer.allocate(4).setAutoExpand(true);
Couple c1 = new Couple((byte)2, "a/b");
Couple c2 = new Couple((byte)1, "c/d/e");
initMultiTopic(m_buff, 123, c1, c2);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
assertEquals(MessageDecoderResult.OK, res);
assertEquals(2, m_mockProtoDecoder.getMessage().subscriptions().size());
}
private void initHeaderBadQos(IoBuffer buff) {
buff.clear().put((byte)(AbstractMessage.SUBSCRIBE << 4)).put((byte)0);
}
private void initMultiTopic(IoBuffer buff, int messageID, Couple... topics) throws IllegalAccessException {
IoBuffer topicBuffer = IoBuffer.allocate(4).setAutoExpand(true);
Utils.writeWord(topicBuffer, messageID);
for (Couple couple : topics) {
topicBuffer.put(Utils.encodeString(couple.getTopic()));
topicBuffer.put((byte)couple.getQos());
}
topicBuffer.flip();
buff.clear().put((byte)(AbstractMessage.SUBSCRIBE << 4 | (byte)0x02))
.put(Utils.encodeRemainingLength(topicBuffer.remaining()));
buff.put(topicBuffer);
}
}
@@ -0,0 +1,70 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.UnsubscribeEncoder;
import org.dna.mqtt.moquette.proto.messages.UnsubscribeMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.dna.mqtt.moquette.proto.TestUtils.*;
/**
*
* @author andrea
*/
public class SubscribeEncoderTest {
UnsubscribeMessage m_msg;
UnsubscribeEncoder m_encoder;
TestUtils.MockProtocolEncoderOutput m_mockProtoEncoder;
@Before
public void setUp() {
m_encoder = new UnsubscribeEncoder();
m_mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
m_msg = new UnsubscribeMessage();
m_msg.setMessageID(0xAABB);
}
@Test
public void testEncodeWithMultiTopic() throws Exception {
m_msg.setQos(QOSType.LEAST_ONE);
//variable part
String topic1 = "a/b";
String topic2 = "a/b/c";
m_msg.addTopic(topic1);
m_msg.addTopic(topic2);
//Exercise
m_encoder.encode(null, m_msg, m_mockProtoEncoder);
//Verify
assertEquals((byte)0xA2, (byte)m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(18, m_mockProtoEncoder.getBuffer().get()); //remaining length
//verify M1ssageID
assertEquals((byte)0xAA, m_mockProtoEncoder.getBuffer().get());
assertEquals((byte)0xBB, m_mockProtoEncoder.getBuffer().get());
//Variable part
verifyString(topic1, m_mockProtoEncoder.getBuffer());
verifyString(topic2, m_mockProtoEncoder.getBuffer());
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_empty_topics() throws Exception {
m_msg.setQos(QOSType.LEAST_ONE);
//Exercise
m_encoder.encode(null, m_msg, m_mockProtoEncoder);
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_badQos() throws Exception {
m_msg.setQos(QOSType.EXACTLY_ONCE);
//Exercise
m_encoder.encode(null, m_msg, m_mockProtoEncoder);
}
}
@@ -0,0 +1,98 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.Utils;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import java.io.UnsupportedEncodingException;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilter.NextFilter;
import org.apache.mina.core.future.WriteFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class TestUtils {
static final class MockProtocolDecoderOutput<T extends AbstractMessage> implements ProtocolDecoderOutput {
protected T m_connMessage;
public T getMessage() {
return m_connMessage;
}
public void write(Object message) {
m_connMessage = (T) message;
}
public void flush(NextFilter nextFilter, IoSession session) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
static final class MockProtocolEncoderOutput implements ProtocolEncoderOutput {
protected IoBuffer m_buffer;
public IoBuffer getBuffer() {
return m_buffer;
}
public void write(Object buffer) {
m_buffer = (IoBuffer) buffer;
}
public void mergeAll() {
throw new UnsupportedOperationException("Not supported yet.");
}
public WriteFuture flush() {
throw new UnsupportedOperationException("Not supported yet.");
}
}
/**
* Verify the presence of the given string starting from the current position
* inside the buffer.
*/
static void verifyString(String str, IoBuffer buff) throws UnsupportedEncodingException {
IoBuffer tmpBuff = IoBuffer.allocate(2).setAutoExpand(true);
byte[] raw = str.getBytes("UTF-8");
Utils.writeWord(tmpBuff, raw.length);
tmpBuff.put(raw).flip();
verifyIoBuffer(tmpBuff, buff);
}
static void verifyIoBuffer(IoBuffer expected, IoBuffer found) {
while(expected.hasRemaining()) {
assertEquals(expected.get(), found.get());
}
}
/**
* Verify that the given bytes buffer og the given numBytes length is present
* in the buff starting from the current position.
*/
static void verifyBuff(int numBytes, byte[] bytes, IoBuffer buff) {
assertTrue(numBytes <= buff.remaining());
byte[] toCheck = new byte[numBytes];
buff.get(toCheck);
for (int i = 0; i < numBytes; i++) {
assertEquals(bytes[i], toCheck[i]);
}
}
static void verifyEquals(byte[] expected, byte[] found) {
assertEquals(expected.length, found.length);
for (int i = 0; i < expected.length; i++) {
assertEquals(expected[i], found[i]);
}
}
}
@@ -0,0 +1,79 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.Utils;
import org.dna.mqtt.moquette.proto.UnsubscribeDecoder;
import org.dna.mqtt.moquette.proto.messages.UnsubscribeMessage;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import org.dna.mqtt.moquette.proto.TestUtils.MockProtocolDecoderOutput;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
/**
*
* @author andrea
*/
public class UnsubscribeDecoderTest {
IoBuffer m_buff;
UnsubscribeDecoder m_msgdec;
MockProtocolDecoderOutput<UnsubscribeMessage> m_mockProtoDecoder;
@Before
public void setUp() {
m_buff = IoBuffer.allocate(4);
m_msgdec = new UnsubscribeDecoder();
m_mockProtoDecoder = new MockProtocolDecoderOutput<UnsubscribeMessage>();
}
@Test
public void testBadQos() throws Exception {
m_buff = IoBuffer.allocate(2);
initHeaderBadQos(m_buff);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
assertEquals(MessageDecoderResult.NOT_OK, res);
}
@Test
public void testMultiTopic() throws Exception {
m_buff = IoBuffer.allocate(4).setAutoExpand(true);
String topic1 = "a/b";
String topic2 = "c/d/e";
initMultiTopic(m_buff, 123, topic1, topic2);
m_buff.flip();
//Excercise
MessageDecoderResult res = m_msgdec.decode(null, m_buff, m_mockProtoDecoder);
//Verify
assertEquals(MessageDecoderResult.OK, res);
assertEquals(2, m_mockProtoDecoder.getMessage().topics().size());
assertEquals(topic1, m_mockProtoDecoder.getMessage().topics().get(0));
assertEquals(topic2, m_mockProtoDecoder.getMessage().topics().get(1));
}
private void initHeaderBadQos(IoBuffer buff) {
buff.clear().put((byte)(AbstractMessage.UNSUBSCRIBE << 4)).put((byte)0);
}
private void initMultiTopic(IoBuffer buff, int messageID, String... topics) throws IllegalAccessException {
IoBuffer topicBuffer = IoBuffer.allocate(4).setAutoExpand(true);
Utils.writeWord(topicBuffer, messageID);
for (String topic : topics) {
topicBuffer.put(Utils.encodeString(topic));
}
topicBuffer.flip();
buff.clear().put((byte)(AbstractMessage.SUBSCRIBE << 4 | (byte)0x02))
.put(Utils.encodeRemainingLength(topicBuffer.remaining()));
buff.put(topicBuffer);
}
}
@@ -0,0 +1,77 @@
package org.dna.mqtt.moquette.proto;
import org.dna.mqtt.moquette.proto.SubscribeEncoder;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage.Couple;
import org.dna.mqtt.moquette.proto.messages.SubscribeMessage;
import org.dna.mqtt.moquette.proto.messages.AbstractMessage.QOSType;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.dna.mqtt.moquette.proto.TestUtils.*;
/**
*
* @author andrea
*/
public class UnsubscribeEncoderTest {
SubscribeEncoder m_encoder;
TestUtils.MockProtocolEncoderOutput m_mockProtoEncoder;
@Before
public void setUp() {
m_encoder = new SubscribeEncoder();
m_mockProtoEncoder = new TestUtils.MockProtocolEncoderOutput();
}
@Test
public void testEncodeWithMultiTopic() throws Exception {
SubscribeMessage msg = new SubscribeMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setMessageID(0xAABB);
//variable part
Couple c1 = new Couple((byte)1, "a/b");
Couple c2 = new Couple((byte)0, "a/b/c");
msg.addSubscription(c1);
msg.addSubscription(c2);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
//Verify
assertEquals((byte)0x82, (byte)m_mockProtoEncoder.getBuffer().get()); //1 byte
assertEquals(20, m_mockProtoEncoder.getBuffer().get()); //remaining length
//verify M1ssageID
assertEquals((byte)0xAA, m_mockProtoEncoder.getBuffer().get());
assertEquals((byte)0xBB, m_mockProtoEncoder.getBuffer().get());
//Variable part
verifyString(c1.getTopic(), m_mockProtoEncoder.getBuffer());
assertEquals(c1.getQos(), m_mockProtoEncoder.getBuffer().get());
verifyString(c2.getTopic(), m_mockProtoEncoder.getBuffer());
assertEquals(c2.getQos(), m_mockProtoEncoder.getBuffer().get());
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_empty_subscription() throws Exception {
SubscribeMessage msg = new SubscribeMessage();
msg.setQos(QOSType.LEAST_ONE);
msg.setMessageID(0xAABB);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
@Test(expected = IllegalArgumentException.class)
public void testEncode_badQos() throws Exception {
SubscribeMessage msg = new SubscribeMessage();
msg.setQos(QOSType.EXACTLY_ONCE);
msg.setMessageID(0xAABB);
//Exercise
m_encoder.encode(null, msg, m_mockProtoEncoder);
}
}
@@ -0,0 +1,87 @@
package org.dna.mqtt.moquette.server;
import org.apache.mina.core.write.WriteRequest;
import org.dna.mqtt.moquette.proto.messages.ConnAckMessage;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.session.DummySession;
import org.apache.mina.core.session.IoSession;
import org.dna.mqtt.moquette.messaging.spi.IMessaging;
import org.dna.mqtt.moquette.proto.messages.ConnectMessage;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
*
* @author andrea
*/
public class MQTTHandlerTest {
byte m_returnCode;
IoSession m_session;
MQTTHandler m_handler;
ConnectMessage connMsg;
@Before
public void setUp() {
m_handler = new MQTTHandler();
connMsg = new ConnectMessage();
connMsg.setProcotolVersion((byte) 0x03);
m_session = new DummySession();
m_session.getFilterChain().addFirst("MessageCatcher", new IoFilterAdapter() {
@Override
public void filterWrite(NextFilter nextFilter, IoSession session,
WriteRequest writeRequest) throws Exception {
try {
ConnAckMessage buf = (ConnAckMessage) writeRequest.getMessage();
m_returnCode = buf.getReturnCode();
} catch (Exception ex) {
throw new AssertionError("Wrong return code");
}
}
});
}
@Test
public void testHandleConnect_BadProtocol() {
connMsg.setProcotolVersion((byte) 0x02);
//Exercise
m_handler.handleConnect(m_session, connMsg);
//Verify
assertEquals(ConnAckMessage.UNNACEPTABLE_PROTOCOL_VERSION, m_returnCode);
}
@Test
public void testConnect_badClientID() {
connMsg.setClientID("extremely_long_clientID_greater_than_23");
//Exercise
m_handler.handleConnect(m_session, connMsg);
//Verify
assertEquals(ConnAckMessage.IDENTIFIER_REJECTED, m_returnCode);
}
@Test
public void testWill() {
connMsg.setClientID("123");
connMsg.setWillFlag(true);
connMsg.setWillTopic("topic");
connMsg.setWillMessage("Topic message");
IMessaging mockedMessaging = mock(IMessaging.class);
//Exercise
m_handler.setMessaging(mockedMessaging);
m_handler.handleConnect(m_session, connMsg);
//Verify
assertEquals(ConnAckMessage.CONNECTION_ACCEPTED, m_returnCode);
verify(mockedMessaging).publish(eq("topic"), eq("Topic message"), anyByte(), anyBoolean());
}
}