Changed lib structure to be modular
This commit is contained in:
Binary file not shown.
@@ -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à.
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user