wildfire chat server source initial submit

This commit is contained in:
heavyrain2012
2019-03-16 17:04:23 +08:00
parent e59217bd10
commit cab3ca0916
1466 changed files with 528862 additions and 15919 deletions
+79
View File
@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<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">
<parent>
<artifactId>wildfirechat-parent</artifactId>
<groupId>cn.wildfirechat</groupId>
<version>0.10</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>cn.wildfirechat</groupId>
<artifactId>client</artifactId>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.hawtbuf/hawtbuf -->
<dependency>
<groupId>org.fusesource.hawtbuf</groupId>
<artifactId>hawtbuf</artifactId>
<version>1.11</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.fusesource.hawtdispatch/hawtdispatch-transport -->
<dependency>
<groupId>org.fusesource.hawtdispatch</groupId>
<artifactId>hawtdispatch-transport</artifactId>
<version>1.22</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>2.5.0</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.6</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,234 @@
package cn.wildfirechat.client;
import cn.wildfirechat.proto.WFCMessage;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import com.xiaoleilu.loServer.pojos.InputRoute;
import io.moquette.spi.impl.security.AES;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.mqtt.client.*;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Base64;
public class IMClient implements Listener {
private final String userId;
private final String token;
private final String clientId;
private final String host;
private final int port;
protected String mqttServerIp;
protected long mqttServerPort;
private static byte[] commonSecret= {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F};
protected String privateSecret;
private long messageHead;
public IMClient(String userId, String token, String clientId, String host, int port) {
this.userId = userId;
this.token = token;
this.clientId = clientId;
this.host = host;
this.port = port;
AES.init(commonSecret);
}
private transient MQTT mqtt = new MQTT();
private transient CallbackConnection connection = null;
public void connect() {
if(route(userId, token)) {
try {
mqtt.setHost("tcp://" + mqttServerIp + ":" + mqttServerPort);
mqtt.setVersion("1.1");
mqtt.setKeepAlive((short)180);
mqtt.setClientId(clientId);
mqtt.setConnectAttemptsMax(100);
mqtt.setReconnectAttemptsMax(100);
mqtt.setUserName(userId);
byte[] password = AES.AESEncrypt(token, privateSecret);
mqtt.setPassword(new UTF8Buffer(password));
connection = mqtt.callbackConnection();
connection.listener(this);
connection.connect(new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
if (value != null) {
try {
WFCMessage.ConnectAckPayload ackPayload = WFCMessage.ConnectAckPayload.parseFrom(value);
System.out.println(ackPayload.getMsgHead());
messageHead = ackPayload.getMsgHead();
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
}
System.out.println("on connect success");
}
@Override
public void onFailure(Throwable value) {
System.out.println("on connect failure");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void sendMessage(WFCMessage.Conversation conversation, WFCMessage.MessageContent messageContent) {
WFCMessage.Message message = WFCMessage.Message.newBuilder().setConversation(conversation).setContent(messageContent).setFromUser(userId).build();
byte[] data = message.toByteArray();
data = AES.AESEncrypt(data, privateSecret);
connection.publish("MS", data, QoS.AT_LEAST_ONCE, false, new Callback<byte[]>() {
@Override
public void onSuccess(byte[] value) {
}
@Override
public void onFailure(Throwable value) {
}
});
}
protected boolean route(String userId, String token) {
HttpPost httpPost;
try{
HttpClient httpClient = HttpClientBuilder.create().build();
httpPost = new HttpPost("http://" + host + ":" + port + "/route");
InputRoute inputRoute = new InputRoute();
inputRoute.setUserId(userId);
inputRoute.setClientId(clientId);
inputRoute.setToken(token);
WFCMessage.IMHttpWrapper request = WFCMessage.IMHttpWrapper.newBuilder().setClientId(clientId).setToken(token).setRequest("ROUTE").setData(ByteString.copyFrom(token.getBytes())).build();
byte[] data = AES.AESEncrypt(request.toByteArray(), commonSecret);
data = Base64.getEncoder().encode(data);
StringEntity entity = new StringEntity(new String(data), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
httpPost.setEntity(entity);
HttpResponse response = httpClient.execute(httpPost);
if(response != null){
if (response.getStatusLine().getStatusCode() != 200) {
System.out.println("Http response error {" + response.getStatusLine().getStatusCode() + "}");
return false;
}
HttpEntity resEntity = response.getEntity();
if(resEntity != null){
try {
byte[] bytes = new byte[resEntity.getContent().available()];
resEntity.getContent().read(bytes);
if (bytes[0] == 0) {
byte[] bytes1 = new byte[bytes.length -1];
for (int i = 0; i < bytes1.length; i++) {
bytes1[i] = bytes[i+1];
}
byte[] rawData = AES.AESDecrypt(bytes1, "", true);
WFCMessage.RouteResponse routeResponse = WFCMessage.RouteResponse.parseFrom(rawData);
mqttServerIp = routeResponse.getHost();
mqttServerPort = routeResponse.getLongPort();
routeResponse.getShortPort();
return true;
} else {
System.out.println("the route failure:" + bytes[0]);
return false;
}
} catch (IOException e) {
e.printStackTrace();
} catch (UnsupportedOperationException e) {
e.printStackTrace();
}
}
} else {
System.out.println("Http response nil");
}
}catch(Exception ex){
ex.printStackTrace();
}
return false;
}
@Override
public void onConnected() {
System.out.println("onConnected");
}
@Override
public void onDisconnected() {
System.out.println("onDisconnected");
}
@Override
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
System.out.println("onPublish" + topic.toString());
ack.run();
if (topic.toString().equals("MN")) {
try {
WFCMessage.NotifyMessage notifyMessage = WFCMessage.NotifyMessage.parseFrom(body.toByteArray());
WFCMessage.PullMessageRequest request = WFCMessage.PullMessageRequest.newBuilder().setId(messageHead).setType(notifyMessage.getType()).build();
connection.publish("MP", request.toByteArray(), QoS.AT_LEAST_ONCE, false, new Callback<byte[]>(){
@Override
public void onSuccess(byte[] value) {
try {
byte[] data = AES.AESDecrypt(value, privateSecret, true);
try {
WFCMessage.PullMessageResult result = WFCMessage.PullMessageResult.parseFrom(data);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
System.out.println("onSuccess");
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onFailure");
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Throwable value) {
System.out.println("onDisconnected" + value.toString());
}
public static void main(String[] args) {
IMClient client = new IMClient("-C-3-3KK","hN0AF2XX6+o1vf6UIC4Fo5O6E+EWC2nmnZd7xZi4SlU=", "1234", "im.liyufan.win", 80);
client.connect();
try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@@ -0,0 +1,125 @@
package cn.wildfirechat.proto;
public class ProtoConstants {
//message Conversation -> type
public interface ConversationType {
int ConversationType_Private = 0;
int ConversationType_Group = 1;
int ConversationType_ChatRoom = 2;
int ConversationType_Channel = 3;
int ConversationType_Thing = 4;
}
//message GroupInfo -> type
public interface GroupType {
//member can add quit change group name and portrait, owner can do all the operations
int GroupType_Normal = 0;
//every member can add quit change group name and portrait, no one can kickoff others
int GroupType_Free = 1;
//member can only quit, owner can do all the operations
int GroupType_Restricted = 2;
}
//message GroupMember -> type
public interface GroupMemberType {
int GroupMemberType_Normal = 0;
int GroupMemberType_Manager = 1;
int GroupMemberType_Owner = 2;
int GroupMemberType_Silent = 3;
int GroupMemberType_Removed = 4;
}
//message FriendRequest -> status
public interface FriendRequestStatus {
int RequestStatus_Sent = 0;
int RequestStatus_Accepted = 1;
int RequestStatus_Rejected = 2;
}
//message UploadDeviceTokenRequest -> platform
public interface Platform {
int Platform_UNSET = 0;
int Platform_iOS = 1;
int Platform_Android = 2;
int Platform_Windows = 3;
int Platform_OSX = 4;
int Platform_WEB = 5;
}
//message NotifyMessage & PullMessageRequest -> type
public interface PullType {
int Pull_Normal = 0;
int Pull_ChatRoom = 1;
int Pull_Group = 2;
}
//message UserResult -> code
public interface UserResultCode {
int Success = 0;
int NotFound = 1;
int NotModified = 2;
}
//message ChatroomInfo -> state
public interface ChatroomState {
int Chatroom_State_Normal = 0;
int Chatroom_State_NotStart = 1;
int Chatroom_State_End = 2;
}
//message MessageContent -> contentType
public interface ContentType {
int Unknown = 0;
int Text = 1;
int Image = 2;
int Voice = 3;
int Location = 4;
int Video = 5;
int RichMedia = 6;
int Custom = 7;
}
//ModifyGroupInfoRequest -> type
public interface ModifyGroupInfoType {
int Modify_Group_Name = 0;
int Modify_Group_Portrait = 1;
int Modify_Group_Extra = 2;
}
//ModifyGroupInfoRequest -> type
public interface PersistFlag {
int Not_Persist = 0;
int Persist = 1;
int Persist_And_Count = 3;
int Transparent = 4;
}
//ModifyChannelInfoRequest -> type
public interface ModifyChannelInfoType {
int Modify_Channel_Name = 0;
int Modify_Channel_Portrait = 1;
int Modify_Channel_Desc = 2;
int Modify_Channel_Extra = 3;
int Modify_Channel_Secret = 4;
int Modify_Channel_Callback = 5;
int Modify_Channel_OnlyCallback = 6;
}
//Channel -> status
public interface ChannelStatus {
int Channel_Status_Public = 0;
int Channel_Status_Private = 1;
int Channel_Status_Destoryed = 2;
}
public interface UserType {
int UserType_Normal = 0;
int UserType_Robot = 1;
int UserType_Device = 2;
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,27 @@
package com.xiaoleilu.loServer.pojos;
public class InputGetToken {
private String userId;
private String clientId;
public InputGetToken(String userId, String name) {
this.userId = userId;
this.clientId = name;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
}
@@ -0,0 +1,32 @@
package com.xiaoleilu.loServer.pojos;
public class InputRoute {
private String userId;
private String token;
private String clientId;
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
}
@@ -0,0 +1,30 @@
package com.xiaoleilu.loServer.pojos;
public class OutputGetIMTokenData {
private String userId;
private String token;
public OutputGetIMTokenData() {
}
public OutputGetIMTokenData(String userId, String imToken) {
this.userId = userId;
this.token = imToken;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
+169
View File
@@ -0,0 +1,169 @@
package io.moquette.spi.impl.security;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AES {
private static byte[] aes_key= {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x78,0x79,0x7A,0x7B,0x7C,0x7D,0x7E,0x7F};
public static void init(byte[] secret) {
if (secret != null && secret.length == 16) {
aes_key = new byte[16];
for (int i = 0; i < 16; i++) {
aes_key[i] = secret[i];
}
} else {
System.out.println("Error int key error, secret incorrect");
}
}
public static byte[] AESEncrypt(String sSrc, String userKey) {
return AESEncrypt(sSrc.getBytes(), userKey);
}
public static byte[] AESEncrypt(byte[] tobeencrypdata, byte[] aesKey) {
if (aesKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (aesKey.length != 16) {
System.out.print("Key长度不是16位");
return null;
}
try {
SecretKeySpec skeySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");//"算法/模式/补码方式"
IvParameterSpec iv = new IvParameterSpec(aesKey);//使用CBC模式,需要一个向量iv,可增加加密算法的强度
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
//2018.1.1 0:0:0 以来的小时数
int curhour = (int) ((System.currentTimeMillis()/1000 - 1514736000)/3600);
byte[] tobeencrypdatawithtime = new byte[tobeencrypdata.length + 4];
byte byte0 = (byte)(curhour & 0xFF);
tobeencrypdatawithtime[0] = byte0;
byte byte1 = (byte)((curhour & 0xFF00) >> 8);
tobeencrypdatawithtime[1] = byte1;
byte byte2 = (byte)((curhour & 0xFF0000) >> 16);
tobeencrypdatawithtime[2] = byte2;
byte byte3 = (byte)((curhour & 0xFF) >> 24);
tobeencrypdatawithtime[3] = byte3;
System.arraycopy(tobeencrypdata, 0, tobeencrypdatawithtime, 4, tobeencrypdata.length);
byte[] encrypted = cipher.doFinal(tobeencrypdatawithtime);
return encrypted;
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
public static byte[] AESEncrypt(byte[] tobeencrypdata, String userKey) {
byte[] aesKey = aes_key;
if (userKey != null && !userKey.isEmpty()) {
aesKey = convertUserKey(userKey);
}
return AESEncrypt(tobeencrypdata, aesKey);
}
public static int getUnsignedByte (byte data){ //将data字节型数据转换为0~255 (0xFF 即BYTE)。
return data&0x0FF ;
}
private static byte[] convertUserKey(String userKey) {
byte[] key = new byte[16];
for (int i = 0; i < 16; i++) {
key[i] = (byte) (userKey.charAt(i) & 0xFF);
}
return key;
}
public static byte[] AESDecrypt(byte[] sSrc, String userKey, boolean checkTime) {
try {
byte[] aesKey = aes_key;
if (userKey != null && !userKey.isEmpty()) {
aesKey = convertUserKey(userKey);
}
// 判断Key是否正确
if (aesKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (aesKey.length != 16) {
System.out.print("Key长度不是16位");
return null;
}
SecretKeySpec skeySpec = new SecretKeySpec(aesKey, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec iv = new IvParameterSpec(aesKey);
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
try {
byte[] original = cipher.doFinal(sSrc);
int hours = 0;
if (original.length > 4) {
hours += getUnsignedByte(original[3]);
hours <<= 8;
hours += getUnsignedByte(original[2]);
hours <<= 8;
hours += getUnsignedByte(original[1]);
hours <<= 8;
hours += getUnsignedByte(original[0]);
//2018.1.1 0:0:0 以来的小时数
int curhour = (int) ((System.currentTimeMillis()/1000 - 1514736000)/3600);
if (curhour - hours > 24 && checkTime) {
return null;
}
byte[] neworiginal = new byte[original.length - 4];
System.arraycopy(original, 4, neworiginal, 0, neworiginal.length);
return neworiginal;
}
return null;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
}
+272
View File
@@ -0,0 +1,272 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.cli;
import org.fusesource.mqtt.client.*;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.Task;
import org.fusesource.mqtt.client.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Listener {
private final MQTT mqtt = new MQTT();
private final ArrayList<Topic> topics = new ArrayList<Topic>();
private boolean debug;
private boolean showTopic;
private static void displayHelpAndExit(int exitCode) {
stdout("");
stdout("This is a simple mqtt client that will subscribe to topics and print all messages it receives.");
stdout("");
stdout("Arguments: [-h host] [-k keepalive] [-c] [-i id] [-u username [-p password]]");
stdout(" [--will-topic topic [--will-payload payload] [--will-qos qos] [--will-retain]]");
stdout(" [-d] [-s]");
stdout(" ( [-q qos] -t topic )+");
stdout("");
stdout("");
stdout(" -h : mqtt host uri to connect to. Defaults to tcp://localhost:1883.");
stdout(" -k : keep alive in seconds for this client. Defaults to 60.");
stdout(" -c : disable 'clean session' (store subscription and pending messages when client disconnects).");
stdout(" -i : id to use for this client. Defaults to a random id.");
stdout(" -u : provide a username (requires MQTT 3.1 broker)");
stdout(" -p : provide a password (requires MQTT 3.1 broker)");
stdout(" --will-topic : the topic on which to publish the client Will.");
stdout(" --will-payload : payload for the client Will, which is sent by the broker in case of");
stdout(" unexpected disconnection. If not given and will-topic is set, a zero");
stdout(" length message will be sent.");
stdout(" --will-qos : QoS level for the client Will.");
stdout(" --will-retain : if given, make the client Will retained.");
stdout(" -d : dispaly debug info on stderr");
stdout(" -s : show message topics in output");
stdout(" -q : quality of service level to use for the subscription. Defaults to 0.");
stdout(" -t : mqtt topic to subscribe to. May be repeated multiple times.");
stdout(" -v : MQTT version to use 3.1 or 3.1.1. (default: 3.1)");
stdout("");
System.exit(exitCode);
}
private static void stdout(Object x) {
System.out.println(x);
}
private static void stderr(Object x) {
System.err.println(x);
}
private static String shift(LinkedList<String> argl) {
if(argl.isEmpty()) {
stderr("Invalid usage: Missing argument");
displayHelpAndExit(1);
}
return argl.removeFirst();
}
public static void main(String[] args) throws Exception {
Listener main = new Listener();
// Process the arguments
QoS qos = QoS.AT_MOST_ONCE;
LinkedList<String> argl = new LinkedList<String>(Arrays.asList(args));
while (!argl.isEmpty()) {
try {
String arg = argl.removeFirst();
if ("--help".equals(arg)) {
displayHelpAndExit(0);
} else if ("-v".equals(arg)) {
main.mqtt.setVersion(shift(argl));
} else if ("-h".equals(arg)) {
main.mqtt.setHost(shift(argl));
} else if ("-k".equals(arg)) {
main.mqtt.setKeepAlive(Short.parseShort(shift(argl)));
} else if ("-c".equals(arg)) {
main.mqtt.setCleanSession(false);
} else if ("-i".equals(arg)) {
main.mqtt.setClientId(shift(argl));
} else if ("-u".equals(arg)) {
main.mqtt.setUserName(shift(argl));
} else if ("-p".equals(arg)) {
main.mqtt.setPassword(shift(argl));
} else if ("--will-topic".equals(arg)) {
main.mqtt.setWillTopic(shift(argl));
} else if ("--will-payload".equals(arg)) {
main.mqtt.setWillMessage(shift(argl));
} else if ("--will-qos".equals(arg)) {
int v = Integer.parseInt(shift(argl));
if( v > QoS.values().length ) {
stderr("Invalid qos value : " + v);
displayHelpAndExit(1);
}
main.mqtt.setWillQos(QoS.values()[v]);
} else if ("--will-retain".equals(arg)) {
main.mqtt.setWillRetain(true);
} else if ("-d".equals(arg)) {
main.debug = true;
} else if ("-s".equals(arg)) {
main.showTopic = true;
} else if ("-q".equals(arg)) {
int v = Integer.parseInt(shift(argl));
if( v > QoS.values().length ) {
stderr("Invalid qos value : " + v);
displayHelpAndExit(1);
}
qos = QoS.values()[v];
} else if ("-t".equals(arg)) {
main.topics.add(new Topic(shift(argl), qos));
} else {
stderr("Invalid usage: unknown option: " + arg);
displayHelpAndExit(1);
}
} catch (NumberFormatException e) {
stderr("Invalid usage: argument not a number");
displayHelpAndExit(1);
}
}
if (main.topics.isEmpty()) {
stderr("Invalid usage: no topics specified.");
displayHelpAndExit(1);
}
main.execute();
System.exit(0);
}
private void execute() {
final CallbackConnection connection = mqtt.callbackConnection();
final CountDownLatch done = new CountDownLatch(1);
// Handle a Ctrl-C event cleanly.
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
setName("MQTT client shutdown");
if(debug) {
stderr("Disconnecting the client.");
}
connection.getDispatchQueue().execute(new Task() {
public void run() {
connection.disconnect(new Callback<Void>() {
public void onSuccess(Void value) {
done.countDown();
}
public void onFailure(Throwable value) {
done.countDown();
}
});
}
});
}
});
connection.listener(new org.fusesource.mqtt.client.Listener() {
public void onConnected() {
if (debug) {
stderr("Connected");
}
}
public void onDisconnected() {
if (debug) {
stderr("Disconnected");
}
}
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
try {
if (showTopic) {
stdout("");
stdout("Topic: " + topic);
body.writeTo(System.out);
stdout("");
} else {
body.writeTo(System.out);
}
ack.run();
} catch (IOException e) {
onFailure(e);
}
}
public void onFailure(Throwable value) {
if (debug) {
value.printStackTrace();
} else {
stderr(value);
}
System.exit(2);
}
});
connection.resume();
connection.connect(new Callback<byte[]>() {
public void onFailure(Throwable value) {
if (debug) {
value.printStackTrace();
} else {
stderr(value);
}
System.exit(2);
}
public void onSuccess(byte[] value) {
final Topic[] ta = topics.toArray(new Topic[topics.size()]);
connection.subscribe(ta, new Callback<byte[]>() {
public void onSuccess(byte[] value) {
if(debug) {
for (int i = 0; i < value.length; i++) {
stderr("Subscribed to Topic: " + ta[i].name() + " with QoS: " + QoS.values()[value[i]]);
}
}
}
public void onFailure(Throwable value) {
stderr("Subscribe failed: " + value);
if(debug) {
value.printStackTrace();
}
System.exit(2);
}
});
}
});
try {
done.await();
} catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
}
+325
View File
@@ -0,0 +1,325 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.cli;
import org.fusesource.mqtt.client.Callback;
import org.fusesource.mqtt.client.CallbackConnection;
import org.fusesource.mqtt.client.Listener;
import org.fusesource.mqtt.client.MQTT;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.AsciiBuffer;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.ByteArrayOutputStream;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.Task;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Publisher {
private final MQTT mqtt = new MQTT();
private QoS qos = QoS.AT_MOST_ONCE;
private UTF8Buffer topic;
private Buffer body;
private boolean debug;
private boolean retain;
private long count = 1;
private long sleep;
private boolean prefixCounter;
private static void displayHelpAndExit(int exitCode) {
stdout("");
stdout("This is a simple mqtt client that will publish to a topic.");
stdout("");
stdout("Arguments: [-h host] [-k keepalive] [-c] [-i id] [-u username [-p password]]");
stdout(" [--will-topic topic [--will-payload payload] [--will-qos qos] [--will-retain]]");
stdout(" [-d] [-n count] [-s sleep] [-q qos] [-r] -t topic ( -pc | -m message | -z | -f file )");
stdout("");
stdout("");
stdout(" -h : mqtt host uri to connect to. Defaults to tcp://localhost:1883.");
stdout(" -k : keep alive in seconds for this client. Defaults to 60.");
stdout(" -c : disable 'clean session'.");
stdout(" -i : id to use for this client. Defaults to a random id.");
stdout(" -u : provide a username (requires MQTT 3.1 broker)");
stdout(" -p : provide a password (requires MQTT 3.1 broker)");
stdout(" --will-topic : the topic on which to publish the client Will.");
stdout(" --will-payload : payload for the client Will, which is sent by the broker in case of");
stdout(" unexpected disconnection. If not given and will-topic is set, a zero");
stdout(" length message will be sent.");
stdout(" --will-qos : QoS level for the client Will.");
stdout(" --will-retain : if given, make the client Will retained.");
stdout(" -d : display debug info on stderr");
stdout(" -n : the number of times to publish the message");
stdout(" -s : the number of milliseconds to sleep between publish operations (defaut: 0)");
stdout(" -q : quality of service level to use for the publish. Defaults to 0.");
stdout(" -r : message should be retained.");
stdout(" -t : mqtt topic to publish to.");
stdout(" -m : message payload to send.");
stdout(" -z : send a null (zero length) message.");
stdout(" -f : send the contents of a file as the message.");
stdout(" -pc : prefix a message counter to the message");
stdout(" -v : MQTT version to use 3.1 or 3.1.1. (default: 3.1)");
stdout("");
System.exit(exitCode);
}
private static void stdout(Object x) {
System.out.println(x);
}
private static void stderr(Object x) {
System.err.println(x);
}
private static String shift(LinkedList<String> argl) {
if(argl.isEmpty()) {
stderr("Invalid usage: Missing argument");
displayHelpAndExit(1);
}
return argl.removeFirst();
}
public static void main(String[] args) throws Exception {
Publisher main = new Publisher();
// Process the arguments
LinkedList<String> argl = new LinkedList<String>(Arrays.asList(args));
while (!argl.isEmpty()) {
try {
String arg = argl.removeFirst();
if ("--help".equals(arg)) {
displayHelpAndExit(0);
} else if ("-v".equals(arg)) {
main.mqtt.setVersion(shift(argl));
} else if ("-h".equals(arg)) {
main.mqtt.setHost(shift(argl));
} else if ("-k".equals(arg)) {
main.mqtt.setKeepAlive(Short.parseShort(shift(argl)));
} else if ("-c".equals(arg)) {
main.mqtt.setCleanSession(false);
} else if ("-i".equals(arg)) {
main.mqtt.setClientId(shift(argl));
} else if ("-u".equals(arg)) {
main.mqtt.setUserName(shift(argl));
} else if ("-p".equals(arg)) {
main.mqtt.setPassword(shift(argl));
} else if ("--will-topic".equals(arg)) {
main.mqtt.setWillTopic(shift(argl));
} else if ("--will-payload".equals(arg)) {
main.mqtt.setWillMessage(shift(argl));
} else if ("--will-qos".equals(arg)) {
int v = Integer.parseInt(shift(argl));
if( v > QoS.values().length ) {
stderr("Invalid qos value : " + v);
displayHelpAndExit(1);
}
main.mqtt.setWillQos(QoS.values()[v]);
} else if ("--will-retain".equals(arg)) {
main.mqtt.setWillRetain(true);
} else if ("-d".equals(arg)) {
main.debug = true;
} else if ("-n".equals(arg)) {
main.count = Long.parseLong(shift(argl));
} else if ("-s".equals(arg)) {
main.sleep = Long.parseLong(shift(argl));
} else if ("-q".equals(arg)) {
int v = Integer.parseInt(shift(argl));
if( v > QoS.values().length ) {
stderr("Invalid qos value : " + v);
displayHelpAndExit(1);
}
main.qos = QoS.values()[v];
} else if ("-r".equals(arg)) {
main.retain = true;
} else if ("-t".equals(arg)) {
main.topic = new UTF8Buffer(shift(argl));
} else if ("-m".equals(arg)) {
main.body = new UTF8Buffer(shift(argl)+"\n");
} else if ("-z".equals(arg)) {
main.body = new UTF8Buffer("");
} else if ("-f".equals(arg)) {
File file = new File(shift(argl));
RandomAccessFile raf = new RandomAccessFile(file, "r");
try {
byte[] data = new byte[(int) raf.length()];
raf.seek(0);
raf.readFully(data);
main.body = new Buffer(data);
} finally {
raf.close();
}
} else if ("-pc".equals(arg)) {
main.prefixCounter = true;
} else {
stderr("Invalid usage: unknown option: " + arg);
displayHelpAndExit(1);
}
} catch (NumberFormatException e) {
stderr("Invalid usage: argument not a number");
displayHelpAndExit(1);
}
}
if (main.topic == null) {
stderr("Invalid usage: no topic specified.");
displayHelpAndExit(1);
}
if (main.body == null) {
stderr("Invalid usage: -z -m or -f must be specified.");
displayHelpAndExit(1);
}
main.execute();
System.exit(0);
}
private void execute() {
final CallbackConnection connection = mqtt.callbackConnection();
final CountDownLatch done = new CountDownLatch(1);
// Handle a Ctrl-C event cleanly.
Runtime.getRuntime().addShutdownHook(new Thread(){
@Override
public void run() {
setName("MQTT client shutdown");
connection.getDispatchQueue().execute(new Task() {
public void run() {
connection.disconnect(new Callback<Void>() {
public void onSuccess(Void value) {
done.countDown();
}
public void onFailure(Throwable value) {
done.countDown();
}
});
}
});
}
});
connection.listener(new Listener() {
public void onConnected() {
if (debug) {
stderr("Connected");
}
}
public void onDisconnected() {
if (debug) {
stderr("Disconnected");
}
}
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
}
public void onFailure(Throwable value) {
if (debug) {
value.printStackTrace();
} else {
stderr(value);
}
System.exit(2);
}
});
connection.resume();
connection.connect(new Callback<byte[]>() {
public void onFailure(Throwable value) {
if (debug) {
value.printStackTrace();
} else {
stderr(value);
}
System.exit(2);
}
public void onSuccess(byte[] value) {
}
});
new Task() {
private long sent = 0;
public void run() {
final Task publish = this;
Buffer message = body;
if(prefixCounter) {
long id = sent + 1;
ByteArrayOutputStream os = new ByteArrayOutputStream(message.length + 15);
os.write(new AsciiBuffer(Long.toString(id)));
os.write(':');
os.write(body);
message = os.toBuffer();
}
connection.publish(topic, message, qos, retain, new Callback<byte[]>() {
public void onSuccess(byte[] value) {
sent ++;
if(debug) {
stdout("Sent message #"+sent);
}
if( sent < count ) {
if(sleep>0) {
System.out.println("Sleeping");
connection.getDispatchQueue().executeAfter(sleep, TimeUnit.MILLISECONDS, publish);
} else {
connection.getDispatchQueue().execute(publish);
}
} else {
connection.disconnect(new Callback<Void>() {
public void onSuccess(Void value) {
done.countDown();
}
public void onFailure(Throwable value) {
done.countDown();
}
});
}
}
public void onFailure(Throwable value) {
stderr("Publish failed: " + value);
if(debug) {
value.printStackTrace();
}
System.exit(2);
}
});
}
}.run();
try {
done.await();
} catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
}
@@ -0,0 +1,154 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import static org.fusesource.hawtbuf.Buffer.utf8;
/**
* <p>
* A blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class BlockingConnection {
private final FutureConnection next;
public BlockingConnection(FutureConnection next) {
this.next = next;
}
public boolean isConnected() {
return next.isConnected();
}
public void connect() throws Exception {
this.next.connect().await();
}
public void disconnect() throws Exception {
this.next.disconnect().await();
}
public void kill() throws Exception {
this.next.kill().await();
}
public byte[] subscribe(final Topic[] topics) throws Exception {
return this.next.subscribe(topics).await();
}
public void unsubscribe(final String[] topics) throws Exception {
this.next.unsubscribe(topics).await();
}
public void unsubscribe(final UTF8Buffer[] topics) throws Exception {
this.next.unsubscribe(topics).await();
}
public void publish(final UTF8Buffer topic, final Buffer payload, final QoS qos, final boolean retain) throws Exception {
this.next.publish(topic, payload, qos, retain).await();
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of overridden methods use File | Settings | File Templates.
}
public void publish(final String topic, final byte[] payload, final QoS qos, final boolean retain) throws Exception {
publish(utf8(topic), new Buffer(payload), qos, retain);
}
public Message receive() throws Exception {
return this.next.receive().await();
}
/**
* @return null if the receive times out.
*/
public Message receive(long amount, TimeUnit unit) throws Exception {
Future<Message> receive = this.next.receive();
try {
Message message = receive.await(amount, unit);
if( message!=null ) {
message.blocking = true;
}
return message;
} catch (TimeoutException e) {
// Put it back on the queue..
receive.then(new Callback<Message>() {
public void onSuccess(final Message value) {
next.putBackMessage(value);
}
public void onFailure(Throwable value) {
}
});
return null;
}
}
public void setReceiveBuffer(final long receiveBuffer) throws InterruptedException {
final CountDownLatch done = new CountDownLatch(1);
next.getDispatchQueue().execute(new Runnable() {
public void run() {
try {
next.setReceiveBuffer(receiveBuffer);
} finally {
done.countDown();
}
}
});
done.await();
}
public long getReceiveBuffer() throws InterruptedException {
final CountDownLatch done = new CountDownLatch(1);
final AtomicLong result = new AtomicLong();
next.getDispatchQueue().execute(new Runnable() {
public void run() {
try {
result.set(next.getReceiveBuffer());
} finally {
done.countDown();
}
}
});
done.await();
return result.get();
}
public void resume() {
next.resume();
}
public void suspend() {
next.suspend();
}
}
@@ -0,0 +1,31 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface Callback<T> {
public void onSuccess(T value);
public void onFailure(Throwable value);
}
@@ -0,0 +1,966 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.HexSupport;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.Dispatch;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
import org.fusesource.hawtdispatch.transport.DefaultTransportListener;
import org.fusesource.hawtdispatch.transport.HeartBeatMonitor;
import org.fusesource.hawtdispatch.transport.SslTransport;
import org.fusesource.hawtdispatch.transport.TcpTransport;
import org.fusesource.hawtdispatch.transport.Transport;
import org.fusesource.mqtt.codec.CONNACK;
import org.fusesource.mqtt.codec.DISCONNECT;
import org.fusesource.mqtt.codec.MQTTFrame;
import org.fusesource.mqtt.codec.MQTTProtocolCodec;
import org.fusesource.mqtt.codec.MessageSupport.Acked;
import org.fusesource.mqtt.codec.PINGREQ;
import org.fusesource.mqtt.codec.PINGRESP;
import org.fusesource.mqtt.codec.PUBACK;
import org.fusesource.mqtt.codec.PUBCOMP;
import org.fusesource.mqtt.codec.PUBLISH;
import org.fusesource.mqtt.codec.PUBREC;
import org.fusesource.mqtt.codec.PUBREL;
import org.fusesource.mqtt.codec.SUBACK;
import org.fusesource.mqtt.codec.SUBSCRIBE;
import org.fusesource.mqtt.codec.UNSUBACK;
import org.fusesource.mqtt.codec.UNSUBSCRIBE;
import javax.net.ssl.SSLContext;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.SocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.fusesource.hawtbuf.Buffer.utf8;
import static org.fusesource.hawtdispatch.Dispatch.createQueue;
/**
* <p>
* A callback based non/blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class CallbackConnection {
private static class Request {
private final MQTTFrame frame;
private final short id;
private final Callback cb;
Request(int id, MQTTFrame frame, Callback cb) {
this.id = (short) id;
this.cb = cb;
this.frame = frame;
}
}
private static final ExtendedListener DEFAULT_LISTENER = new ExtendedListener(){
public void onConnected() {
}
public void onDisconnected() {
}
public void onPublish(UTF8Buffer utf8Buffer, Buffer buffer, Runnable runnable) {
this.onFailure(createListenerNotSetError());
}
public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<byte[]>> ack) {
this.onFailure(createListenerNotSetError());
}
public void onFailure(Throwable value) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), value);
}
};
private final DispatchQueue queue;
private final MQTT mqtt;
private Transport transport;
private ExtendedListener listener = DEFAULT_LISTENER;
private Runnable refiller;
private Map<Short, Request> requests = new ConcurrentHashMap<Short, Request>();
private LinkedList<Request> overflow = new LinkedList<Request>();
private final HashMap<Short, Callback<byte[]>> processed = new HashMap<Short, Callback<byte[]>>();
private Throwable failure;
private boolean disconnected = false;
private HeartBeatMonitor heartBeatMonitor;
private long pingedAt;
private long reconnects = 0;
private final AtomicInteger suspendCount = new AtomicInteger(0);
private final AtomicInteger suspendChanges = new AtomicInteger(0);
private final HashMap<UTF8Buffer, QoS> activeSubs = new HashMap<UTF8Buffer, QoS>();
public CallbackConnection(MQTT mqtt) {
this.mqtt = mqtt;
if(this.mqtt.dispatchQueue == null) {
this.queue = createQueue("mqtt client");
} else {
this.queue = this.mqtt.dispatchQueue;
}
}
public void connect(final Callback<byte[]> cb) {
assert cb !=null : "Callback should not be null.";
if( transport!=null ) {
cb.onFailure(new IllegalStateException("Already connected"));
return;
}
try {
createTransport(new LoginHandler(cb, true));
} catch (Throwable e) {
// This error happens when the MQTT config is invalid, reattempting
// wont fix this case.
cb.onFailure(e);
}
}
void reconnect() {
try {
// And reconnect.
createTransport(new LoginHandler(new Callback<byte[]>() {
public void onSuccess(byte[] value) {
mqtt.tracer.debug("Restoring MQTT connection state");
// Setup a new overflow so that the replay can be sent out before the original overflow list.
LinkedList<Request> originalOverflow = overflow;
Map<Short, Request> originalRequests = requests;
overflow = new LinkedList<Request>();
requests = new ConcurrentHashMap<Short, Request>();
// Restore any active subscriptions.
if (!activeSubs.isEmpty()) {
ArrayList<Topic> topics = new ArrayList<Topic>(activeSubs.size());
for (Map.Entry<UTF8Buffer, QoS> entry : activeSubs.entrySet()) {
topics.add(new Topic(entry.getKey(), entry.getValue()));
}
send(new SUBSCRIBE().topics(topics.toArray(new Topic[topics.size()])), null);
}
// Replay any un-acked requests..
for (Map.Entry<Short, Request> entry : originalRequests.entrySet()) {
MQTTFrame frame = entry.getValue().frame;
frame.dup(true); // set the dup flag as these frames were previously transmitted.
send(entry.getValue());
}
// Replay the original overflow
for (Request request : originalOverflow) {
// Stuff in the overflow never got sent out.. so no need to set the dup flag
send(request);
}
}
public void onFailure(Throwable value) {
handleFatalFailure(value);
}
}, false));
} catch (Throwable e) {
handleFatalFailure(e);
}
}
void handleSessionFailure(Throwable error) {
// Socket failure, should we try to reconnect?
if( !disconnected && (mqtt.reconnectAttemptsMax<0 || reconnects < mqtt.reconnectAttemptsMax ) ) {
mqtt.tracer.debug("Reconnecting transport");
// Cleanup the previous transport.
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
final Transport t = transport;
transport = null;
if(t!=null) {
t.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
reconnect();
}
});
} else {
reconnect();
}
} else {
// nope.
handleFatalFailure(error);
}
}
void reconnect(final Callback<Transport> onConnect) {
long reconnectDelay = mqtt.reconnectDelay;
if( reconnectDelay> 0 && mqtt.reconnectBackOffMultiplier > 1.0 ) {
reconnectDelay = (long) Math.pow(mqtt.reconnectDelay*reconnects, mqtt.reconnectBackOffMultiplier);
}
reconnectDelay = Math.min(reconnectDelay, mqtt.reconnectDelayMax);
reconnects += 1;
queue.executeAfter(reconnectDelay, TimeUnit.MILLISECONDS, new Task() {
@Override
public void run() {
if(disconnected) {
onConnect.onFailure(createDisconnectedError());
} else {
try {
createTransport(onConnect);
} catch (Exception e) {
onConnect.onFailure(e);
}
}
}
});
}
/**
* Creates and start a transport to the MQTT server. Passes it to the onConnect
* once the transport is connected.
*
* @param onConnect
* @throws Exception
*/
void createTransport(final Callback<Transport> onConnect) throws Exception {
mqtt.tracer.debug("Connecting");
String scheme = mqtt.host.getScheme();
final Transport transport;
if( "tcp".equals(scheme) ) {
transport = new TcpTransport();
} else if( SslTransport.protocol(scheme)!=null ) {
SslTransport ssl = new SslTransport();
if( mqtt.sslContext == null ) {
mqtt.sslContext = SSLContext.getDefault();
}
ssl.setSSLContext(mqtt.sslContext);
transport = ssl;
} else {
throw new Exception("Unsupported URI scheme '"+scheme+"'");
}
if( mqtt.blockingExecutor == null ) {
mqtt.blockingExecutor = MQTT.getBlockingThreadPool();
}
transport.setBlockingExecutor(mqtt.blockingExecutor);
transport.setDispatchQueue(queue);
transport.setProtocolCodec(new MQTTProtocolCodec());
if( transport instanceof TcpTransport ) {
TcpTransport tcp = (TcpTransport)transport;
tcp.setMaxReadRate(mqtt.maxReadRate);
tcp.setMaxWriteRate(mqtt.maxWriteRate);
tcp.setReceiveBufferSize(mqtt.receiveBufferSize);
tcp.setSendBufferSize(mqtt.sendBufferSize);
tcp.setTrafficClass(mqtt.trafficClass);
tcp.setUseLocalHost(mqtt.useLocalHost);
tcp.connecting(mqtt.host, mqtt.localAddress);
}
transport.setTransportListener(new DefaultTransportListener(){
@Override
public void onTransportConnected() {
mqtt.tracer.debug("Transport connected");
if(disconnected) {
onFailure(createDisconnectedError());
} else {
onConnect.onSuccess(transport);
}
}
@Override
public void onTransportFailure(final IOException error) {
mqtt.tracer.debug("Transport failure: %s", error);
onFailure(error);
}
private void onFailure(final Throwable error) {
if(!transport.isClosed()) {
transport.stop(new Task() {
@Override
public void run() {
onConnect.onFailure(error);
}
});
}
}
});
transport.start(NOOP);
}
class LoginHandler implements Callback<Transport> {
private final Callback<byte[]> cb;
private final boolean initialConnect;
LoginHandler(Callback<byte[]> cb, boolean initialConnect) {
this.cb = cb;
this.initialConnect = initialConnect;
}
public void onSuccess(final Transport transport) {
transport.setTransportListener(new DefaultTransportListener() {
@Override
public void onTransportFailure(IOException error) {
mqtt.tracer.debug("Transport failure: %s", error);
transport.stop(NOOP);
onFailure(error);
}
@Override
public void onTransportCommand(Object command) {
MQTTFrame response = (MQTTFrame) command;
mqtt.tracer.onReceive(response);
try {
switch (response.messageType()) {
case CONNACK.TYPE:
CONNACK connack = new CONNACK().decode(response);
switch (connack.code()) {
case CONNECTION_ACCEPTED:
mqtt.tracer.debug("MQTT login accepted");
onSessionEstablished(transport);
listener.onConnected();
cb.onSuccess(connack.payload);
queue.execute(new Task() {
@Override
public void run() {
drainOverflow();
}
});
break;
default:
mqtt.tracer.debug("MQTT login rejected");
// Bad creds or something. No point in reconnecting.
transport.stop(NOOP);
cb.onFailure(new MQTTException("Could not connect: " + connack.code(), connack));
}
break;
default:
mqtt.tracer.debug("Received unexpected MQTT frame: %d", response.messageType());
// Naughty MQTT server? No point in reconnecting.
transport.stop(NOOP);
cb.onFailure(new IOException("Could not connect. Received unexpected command: " + response.messageType()));
}
} catch (ProtocolException e) {
mqtt.tracer.debug("Protocol error: %s", e);
transport.stop(NOOP);
cb.onFailure(e);
}
}
});
transport.resumeRead();
if( mqtt.connect.clientId() == null ) {
String id = hex(transport.getLocalAddress())+Long.toHexString(System.currentTimeMillis()/1000);
if(id.length() > 23) {
id = id.substring(0,23);
}
mqtt.connect.clientId(utf8(id));
}
MQTTFrame encoded = mqtt.connect.encode();
boolean accepted = transport.offer(encoded);
mqtt.tracer.onSend(encoded);
mqtt.tracer.debug("Logging in");
assert accepted: "First frame should always be accepted by the transport";
}
private boolean tryReconnect() {
if(initialConnect) {
return mqtt.connectAttemptsMax<0 || reconnects < mqtt.connectAttemptsMax;
}
return mqtt.reconnectAttemptsMax<0 || reconnects < mqtt.reconnectAttemptsMax;
}
public void onFailure(Throwable value) {
// Socket failure, should we try to reconnect?
if( !disconnected && tryReconnect() ) {
reconnect(this);
} else {
// nope.
cb.onFailure(value);
}
}
}
private boolean onRefillCalled =false;
public void onSessionEstablished(Transport transport) {
this.transport = transport;
if( suspendCount.get() > 0 ) {
this.transport.suspendRead();
}
this.transport.setTransportListener(new DefaultTransportListener() {
@Override
public void onTransportCommand(Object command) {
MQTTFrame frame = (MQTTFrame) command;
mqtt.tracer.onReceive(frame);
processFrame(frame);
}
@Override
public void onRefill() {
onRefillCalled =true;
drainOverflow();
}
@Override
public void onTransportFailure(IOException error) {
handleSessionFailure(error);
}
});
pingedAt = 0;
if(mqtt.getKeepAlive()>0) {
heartBeatMonitor = new HeartBeatMonitor();
heartBeatMonitor.setWriteInterval((mqtt.getKeepAlive() * 1000) / 2);
heartBeatMonitor.setTransport(this.transport);
heartBeatMonitor.suspendRead(); // to match the suspended state of the transport.
heartBeatMonitor.setOnKeepAlive(new Task() {
@Override
public void run() {
// Don't care if the offer is rejected, just means we have data outbound.
if(!disconnected && pingedAt==0) {
MQTTFrame encoded = new PINGREQ().encode();
if(CallbackConnection.this.transport.offer(encoded)) {
mqtt.tracer.onSend(encoded);
final long now = System.currentTimeMillis();
final long suspends = suspendChanges.get();
pingedAt = now;
queue.executeAfter(CallbackConnection.this.mqtt.getKeepAlive(), TimeUnit.SECONDS, new Task() {
@Override
public void run() {
if (now == pingedAt) {
// if the connection remained suspend we will never get the ping response..
// Looks like the user has forgoton to resume the connection
if (suspends == suspendChanges.get() && suspendCount.get() > 0) {
// Since the connection has been suspended, we can't really
// check to see if we are getting the ping responses.
mqtt.tracer.debug("The connection has remained suspended for an extended period of time so it cannot do proper keep alive processing. Did you forget to resume the connection?");
} else {
mqtt.tracer.debug("Ping timeout");
handleSessionFailure(new ProtocolException("Ping timeout").fillInStackTrace());
}
}
}
});
}
}
}
});
heartBeatMonitor.start();
}
}
public Transport transport() {
return transport;
}
public DispatchQueue getDispatchQueue() {
return queue;
}
public void resume() {
suspendChanges.incrementAndGet();
if( suspendCount.decrementAndGet() == 0 && this.transport!=null ) {
this.transport.resumeRead();
if(this.heartBeatMonitor!=null){
this.heartBeatMonitor.resumeRead();
}
}
}
public void suspend() {
suspendChanges.incrementAndGet();
if( suspendCount.incrementAndGet() == 1 && this.transport!=null ) {
this.transport.suspendRead();
if(this.heartBeatMonitor!=null){
this.heartBeatMonitor.suspendRead();
}
}
}
public CallbackConnection refiller(Runnable refiller) {
queue.assertExecuting();
this.refiller = refiller;
return this;
}
public CallbackConnection listener(final Listener original) {
if( original instanceof ExtendedListener ) {
this.listener = (ExtendedListener) original;
} else {
this.listener = new ExtendedListener() {
public void onPublish(UTF8Buffer topic, Buffer body, final Callback<Callback<byte[]>> ack) {
original.onPublish(topic, body, new Runnable() {
public void run() {
ack.onSuccess(null);
}
});
}
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack) {
original.onPublish(topic, body, ack);
}
public void onConnected() {
original.onConnected();
}
public void onDisconnected() {
original.onDisconnected();
}
public void onFailure(Throwable value) {
original.onFailure(value);
}
};
}
return this;
}
public boolean full() {
queue.assertExecuting();
return this.transport.full();
}
public Throwable failure() {
queue.assertExecuting();
return failure;
}
public void disconnect(final Callback<Void> onComplete) {
if( disconnected ) {
if(onComplete!=null){
onComplete.onSuccess(null);
}
return;
}
disconnected = true;
final short requestId = getNextMessageId();
final Runnable stop = new Runnable() {
private boolean executed = false;
public void run() {
if(!executed) {
executed = true;
requests.remove(requestId);
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
transport.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
if (onComplete != null) {
onComplete.onSuccess(null);
}
}
});
}
}
};
Callback<Void> cb = new Callback<Void>() {
public void onSuccess(Void v) {
// To make sure DISCONNECT has been flushed out to the socket
onRefillCalled = false;
refiller = new Runnable() {
public void run() {
if(onRefillCalled) {
stop.run();
}
}
};
if(transport != null){
transport.flush();
}
}
public void onFailure(Throwable value) {
stop.run();
}
};
// Pop the frame into a request so it we get notified
// of any failures so we continue to stop the transport.
if(transport!=null) {
MQTTFrame frame = new DISCONNECT().encode();
send(new Request(getNextMessageId(), frame, cb));
} else {
cb.onSuccess(null);
}
}
/**
* Kills the connection without a graceful disconnect.
* @param onComplete
*/
public void kill(final Callback<Void> onComplete) {
if( disconnected ) {
if(onComplete!=null){
onComplete.onSuccess(null);
}
return;
}
disconnected = true;
if(heartBeatMonitor!=null) {
heartBeatMonitor.stop();
heartBeatMonitor = null;
}
transport.stop(new Task() {
@Override
public void run() {
listener.onDisconnected();
if (onComplete != null) {
onComplete.onSuccess(null);
}
}
});
}
public void publish(String topic, byte[] payload, QoS qos, boolean retain, Callback<byte[]> cb) {
publish(utf8(topic), new Buffer(payload), qos, retain, cb);
}
public void publish(UTF8Buffer topic, Buffer payload, QoS qos, boolean retain, Callback<byte[]> cb) {
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
PUBLISH command = new PUBLISH().qos(qos).retain(retain);
command.topicName(topic).payload(payload);
send(command, cb);
}
public void subscribe(final Topic[] topics, Callback<byte[]> cb) {
if(topics==null) {
throw new IllegalArgumentException("topics must not be null");
}
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
if( listener == DEFAULT_LISTENER ) {
cb.onFailure(createListenerNotSetError());
} else {
send(new SUBSCRIBE().topics(topics), new ProxyCallback<byte[]>(cb){
@Override
public void onSuccess(byte[] value) {
for (Topic topic : topics) {
activeSubs.put(topic.name(), topic.qos());
}
if(next!=null) {
next.onSuccess(value);
}
}
});
}
}
public void unsubscribe(final UTF8Buffer[] topics, Callback<Void> cb) {
queue.assertExecuting();
if( disconnected ) {
cb.onFailure(createDisconnectedError());
return;
}
send(new UNSUBSCRIBE().topics(topics), new ProxyCallback(cb){
@Override
public void onSuccess(Object value) {
for (UTF8Buffer topic : topics) {
activeSubs.remove(topic);
}
if(next!=null) {
next.onSuccess(value);
}
}
});
}
private void send(Acked command, Callback cb) {
short id = 0;
if(command.qos() != QoS.AT_MOST_ONCE) {
id = getNextMessageId();
command.messageId(id);
}
send(new Request(id, command.encode(), cb));
}
private void send(Request request) {
if( failure !=null ) {
if( request.cb!=null ) {
request.cb.onFailure(failure);
}
} else {
// Put the request in the map before sending it over the wire.
if(request.id!=0) {
this.requests.put(request.id, request);
}
if( overflow.isEmpty() && transport!=null && transport.offer(request.frame) ) {
mqtt.tracer.onSend(request.frame);
if(request.id==0) {
if( request.cb!=null ) {
((Callback<Void>)request.cb).onSuccess(null);
}
}
} else {
// Remove it from the request.
this.requests.remove(request.id);
overflow.addLast(request);
}
}
}
private short nextMessageId = 1;
private short getNextMessageId() {
short rc = nextMessageId;
nextMessageId++;
if(nextMessageId==0) {
nextMessageId=1;
}
return rc;
}
private void drainOverflow() {
queue.assertExecuting();
if( overflow.isEmpty() || transport==null ){
return;
}
Request request;
while((request=overflow.peek())!=null) {
if( this.transport.offer(request.frame) ) {
mqtt.tracer.onSend(request.frame);
overflow.removeFirst();
if(request.id==0) {
if( request.cb!=null ) {
((Callback<Void>)request.cb).onSuccess(null);
}
} else {
this.requests.put(request.id, request);
}
} else {
break;
}
}
if( overflow.isEmpty() ) {
if( refiller!=null ) {
try {
refiller.run();
} catch (Throwable e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
private void completeRequest(short id, byte originalType, Object arg) {
Request request = requests.remove(id);
if( request!=null ) {
assert originalType==request.frame.messageType();
if(request.cb!=null) {
if( arg==null ) {
((Callback<Void>)request.cb).onSuccess(null);
} else {
((Callback<Object>)request.cb).onSuccess(arg);
}
}
} else {
handleFatalFailure(new ProtocolException("Command from server contained an invalid message id: " + id));
}
}
private void processFrame(MQTTFrame frame) {
try {
switch(frame.messageType()) {
case PUBLISH.TYPE: {
PUBLISH publish = new PUBLISH().decode(frame);
toReceiver(publish);
break;
}
case PUBREL.TYPE:{
PUBREL ack = new PUBREL().decode(frame);
Callback<byte[]> onRel = processed.remove(ack.messageId());
PUBCOMP response = new PUBCOMP();
response.messageId(ack.messageId());
send(new Request(0, response.encode(), null));
if( onRel!=null ) {
onRel.onSuccess(null);
}
break;
}
case PUBACK.TYPE:{
PUBACK ack = new PUBACK().decode(frame);
completeRequest(ack.messageId(), PUBLISH.TYPE, ack.payload);
break;
}
case PUBREC.TYPE:{
PUBREC ack = new PUBREC().decode(frame);
PUBREL response = new PUBREL();
response.messageId(ack.messageId());
send(new Request(0, response.encode(), null));
break;
}
case PUBCOMP.TYPE:{
PUBCOMP ack = new PUBCOMP().decode(frame);
completeRequest(ack.messageId(), PUBLISH.TYPE, null);
break;
}
case SUBACK.TYPE: {
SUBACK ack = new SUBACK().decode(frame);
completeRequest(ack.messageId(), SUBSCRIBE.TYPE, ack.grantedQos());
break;
}
case UNSUBACK.TYPE: {
UNSUBACK ack = new UNSUBACK().decode(frame);
completeRequest(ack.messageId(), UNSUBSCRIBE.TYPE, null);
break;
}
case PINGRESP.TYPE: {
pingedAt = 0;
break;
}
default:
throw new ProtocolException("Unexpected MQTT command type: "+frame.messageType());
}
} catch (Throwable e) {
handleFatalFailure(e);
}
}
static public final Task NOOP = Dispatch.NOOP;
private void toReceiver(final PUBLISH publish) {
if( listener !=null ) {
try {
Callback<Callback<byte[]>> cb = null;
switch( publish.qos() ) {
case AT_LEAST_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
PUBACK response = new PUBACK();
response.messageId(publish.messageId());
send(new Request(0, response.encode(), null));
if( value !=null ) {
value.onSuccess(null);
}
}
public void onFailure(Throwable value) {
}
};
break;
case EXACTLY_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
PUBREC response = new PUBREC();
response.messageId(publish.messageId());
processed.put(publish.messageId(), value);
send(new Request(0, response.encode(), null));
}
public void onFailure(Throwable value) {
}
};
// Looks like a dup delivery.. filter it out.
if( processed.get(publish.messageId())!=null ) {
return;
}
break;
case AT_MOST_ONCE:
cb = new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
if (value != null) {
value.onSuccess(null);
}
}
public void onFailure(Throwable value) {
}
};
}
listener.onPublish(publish.topicName(), publish.payload(), cb);
} catch (Throwable e) {
handleFatalFailure(e);
}
}
}
private void handleFatalFailure(Throwable error) {
if( failure == null ) {
failure = error;
mqtt.tracer.debug("Fatal connection failure: %s", error);
// Fail incomplete requests.
ArrayList<Request> values = new ArrayList(requests.values());
requests.clear();
for (Request value : values) {
if( value.cb!= null ) {
value.cb.onFailure(failure);
}
}
ArrayList<Request> overflowEntries = new ArrayList<Request>(overflow);
overflow.clear();
for (Request entry : overflowEntries) {
if( entry.cb !=null ) {
entry.cb.onFailure(failure);
}
}
if( listener !=null && !disconnected ) {
try {
listener.onFailure(failure);
} catch (Exception e) {
Thread.currentThread().getUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), e);
}
}
}
}
private static IllegalStateException createListenerNotSetError() {
return (IllegalStateException) new IllegalStateException("No connection listener set to handle message received from the server.").fillInStackTrace();
}
private static IllegalStateException createDisconnectedError() {
return (IllegalStateException) new IllegalStateException("Disconnected").fillInStackTrace();
}
static private String hex(SocketAddress address) {
if( address instanceof InetSocketAddress ) {
InetSocketAddress isa = (InetSocketAddress)address;
return HexSupport.toHexFromBuffer(new Buffer(isa.getAddress().getAddress()))+Integer.toHexString(isa.getPort());
}
return "";
}
}
@@ -0,0 +1,32 @@
/**
* Copyright (C) 2016, Red Hat Inc. All rights reserved.
*
* http://redhat.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface ExtendedListener extends Listener {
public void onPublish(UTF8Buffer topic, Buffer body, Callback<Callback<byte[]>> ack);
}
@@ -0,0 +1,33 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import java.util.concurrent.TimeUnit;
/**
* <p>A simplified Future function results interface.</p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public interface Future<T> {
T await() throws Exception;
T await(long amount, TimeUnit unit) throws Exception;
void then(Callback<T> callback);
}
@@ -0,0 +1,258 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
import java.util.ArrayList;
import java.util.LinkedList;
import static org.fusesource.hawtbuf.Buffer.utf8;
/**
* <p>
* A Future based optionally-blocking Connection interface to MQTT.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class FutureConnection {
private final CallbackConnection next;
private final LinkedList<Promise<Message>> receiveFutures = new LinkedList<Promise<Message>>();
private final LinkedList<Message> receivedFrames = new LinkedList<Message>();
private volatile boolean connected;
private long receiveBuffer = 1024*1024;
private long receiveBufferRemaining = receiveBuffer;
private boolean receiveBufferFull = false;
public FutureConnection(CallbackConnection next) {
this.next = next;
this.next.listener(new ExtendedListener() {
public void onConnected() {
connected = true;
}
public void onDisconnected() {
connected = false;
}
public void onPublish(UTF8Buffer topic, final Buffer payload, final Callback<Callback<byte[]>> onComplete) {
getDispatchQueue().assertExecuting();
receiveBufferRemaining -= payload.length();
if( !receiveBufferFull && receiveBufferRemaining <=0 ) {
receiveBufferFull = true;
suspend();
}
deliverMessage(new Message(getDispatchQueue(), topic, payload, new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
processed();
onComplete.onSuccess(value);
}
public void onFailure(Throwable value) {
processed();
onComplete.onFailure(value);
}
private void processed() {
getDispatchQueue().assertExecuting();
receiveBufferRemaining += payload.length();
if( receiveBufferFull && receiveBufferRemaining >0 ) {
receiveBufferFull = false;
resume();
}
}
}));
}
public void onPublish(UTF8Buffer topic, Buffer payload, final Runnable onComplete) {
onPublish(topic, payload, new Callback<Callback<byte[]>>() {
public void onSuccess(Callback<byte[]> value) {
onComplete.run();
}
public void onFailure(Throwable value) {
}
});
}
public void onFailure(Throwable value) {
getDispatchQueue().assertExecuting();
ArrayList<Promise<?>> tmp = new ArrayList<Promise<?>>(receiveFutures);
receiveFutures.clear();
for (Promise<?> future : tmp) {
future.onFailure(value);
}
connected = false;
}
});
}
void deliverMessage(Message msg) {
if( receiveFutures.isEmpty() ) {
receivedFrames.add(msg);
} else {
receiveFutures.removeFirst().onSuccess(msg);
}
}
void putBackMessage(Message msg) {
if( receiveFutures.isEmpty() ) {
receivedFrames.addFirst(msg);
} else {
receiveFutures.removeFirst().onSuccess(msg);
}
}
public boolean isConnected() {
return connected;
}
public DispatchQueue getDispatchQueue() {
return this.next.getDispatchQueue();
}
public Future<byte[]> connect() {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.connect(future);
}
});
return future;
}
public Future<Void> disconnect() {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.disconnect(future);
}
});
return future;
}
public Future<Void> kill() {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.kill(future);
}
});
return future;
}
public Future<byte[]> subscribe(final Topic[] topics) {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.subscribe(topics, future);
}
});
return future;
}
public Future<Void> unsubscribe(final String[] topics) {
UTF8Buffer[] buffers = new UTF8Buffer[topics.length];
for (int i = 0; i < buffers.length; i++) {
buffers[i] = new UTF8Buffer(topics[i]);
}
return unsubscribe(buffers);
}
public Future<Void> unsubscribe(final UTF8Buffer[] topics) {
final Promise<Void> future = new Promise<Void>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.unsubscribe(topics, future);
}
});
return future;
}
public Future<byte[]> publish(final String topic, final byte[] payload, final QoS qos, final boolean retain) {
return publish(utf8(topic), new Buffer(payload), qos, retain);
}
public Future<byte[]> publish(final UTF8Buffer topic, final Buffer payload, final QoS qos, final boolean retain) {
final Promise<byte[]> future = new Promise<byte[]>();
next.getDispatchQueue().execute(new Task() {
public void run() {
next.publish(topic, payload, qos, retain, future);
}
});
return future;
}
public Future<Message> receive() {
final Promise<Message> future = new Promise<Message>();
getDispatchQueue().execute(new Task(){
public void run() {
if( next.failure()!=null ) {
future.onFailure(next.failure());
} else {
if( receivedFrames.isEmpty() ) {
receiveFutures.add(future);
} else {
future.onSuccess(receivedFrames.removeFirst());
}
}
}
});
return future;
}
public long getReceiveBuffer() {
getDispatchQueue().assertExecuting();
return receiveBuffer;
}
public void setReceiveBuffer(long receiveBuffer) {
getDispatchQueue().assertExecuting();
long prev = this.receiveBuffer;
this.receiveBuffer = receiveBuffer;
long diff = prev-receiveBuffer;
receiveBufferRemaining -= diff;
if( !receiveBufferFull && receiveBufferRemaining <=0 ) {
receiveBufferFull = true;
suspend();
} else if( receiveBufferFull && receiveBufferRemaining >0 ) {
receiveBufferFull = false;
resume();
}
}
public void resume() {
next.resume();
}
public void suspend() {
next.suspend();
}
}
@@ -0,0 +1,36 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
@Deprecated
public interface Listener {
public void onConnected();
public void onDisconnected();
public void onPublish(UTF8Buffer topic, Buffer body, Runnable ack);
public void onFailure(Throwable value);
}
+392
View File
@@ -0,0 +1,392 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.transport.*;
import org.fusesource.mqtt.codec.CONNECT;
import javax.net.ssl.SSLContext;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.*;
import static org.fusesource.hawtbuf.Buffer.utf8;
import static org.fusesource.hawtdispatch.Dispatch.createQueue;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class MQTT {
private static final long KEEP_ALIVE = Long.parseLong(System.getProperty("mqtt.thread.keep_alive", Integer.toString(1000)));
private static final long STACK_SIZE = Long.parseLong(System.getProperty("mqtt.thread.stack_size", Integer.toString(1024*512)));
private static ThreadPoolExecutor blockingThreadPool;
public synchronized static ThreadPoolExecutor getBlockingThreadPool() {
if( blockingThreadPool == null ) {
blockingThreadPool = new ThreadPoolExecutor(0, Integer.MAX_VALUE, KEEP_ALIVE, TimeUnit.MILLISECONDS, new SynchronousQueue<Runnable>(), new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread rc = new Thread(null, r, "MQTT Task", STACK_SIZE);
rc.setDaemon(true);
return rc;
}
}) {
@Override
public void shutdown() {
// we don't ever shutdown since we are shared..
}
@Override
public List<Runnable> shutdownNow() {
// we don't ever shutdown since we are shared..
return Collections.emptyList();
}
};
}
return blockingThreadPool;
}
public synchronized static void setBlockingThreadPool(ThreadPoolExecutor pool) {
blockingThreadPool = pool;
}
private static final URI DEFAULT_HOST = createDefaultHost();
private static URI createDefaultHost() {
try {
return new URI("tcp://127.0.0.1:1883");
} catch (URISyntaxException e) {
return null;
}
}
protected URI host = DEFAULT_HOST;
protected URI localAddress;
protected SSLContext sslContext;
protected DispatchQueue dispatchQueue;
protected Executor blockingExecutor;
protected int maxReadRate;
protected int maxWriteRate;
protected int trafficClass = TcpTransport.IPTOS_THROUGHPUT;
protected int receiveBufferSize = 1024*64;
protected int sendBufferSize = 1024*64;
protected boolean useLocalHost = true;
protected CONNECT connect = new CONNECT();
protected long reconnectDelay = 10;
protected long reconnectDelayMax = 30*1000;
protected double reconnectBackOffMultiplier = 2.0f;
protected long reconnectAttemptsMax = -1;
protected long connectAttemptsMax = -1;
protected Tracer tracer = new Tracer();
public MQTT() {
}
public MQTT(MQTT other) {
this.host = other.host;
this.localAddress = other.localAddress;
this.sslContext = other.sslContext;
this.dispatchQueue = other.dispatchQueue;
this.blockingExecutor = other.blockingExecutor;
this.maxReadRate = other.maxReadRate;
this.maxWriteRate = other.maxWriteRate;
this.trafficClass = other.trafficClass;
this.receiveBufferSize = other.receiveBufferSize;
this.sendBufferSize = other.sendBufferSize;
this.useLocalHost = other.useLocalHost;
this.connect = new CONNECT(other.connect);
this.reconnectDelay = other.reconnectDelay;
this.reconnectDelayMax = other.reconnectDelayMax;
this.reconnectBackOffMultiplier = other.reconnectBackOffMultiplier;
this.reconnectAttemptsMax = other.reconnectAttemptsMax;
this.connectAttemptsMax = other.connectAttemptsMax;
this.tracer = other.tracer;
}
public CallbackConnection callbackConnection() {
if( !isCleanSession() && ( getClientId()==null || getClientId().length==0 )) {
throw new IllegalArgumentException("The client id MUST be configured when clean session is set to false");
}
return new CallbackConnection(new MQTT(this));
}
public FutureConnection futureConnection() {
return new FutureConnection(callbackConnection());
}
public BlockingConnection blockingConnection() {
return new BlockingConnection(futureConnection());
}
public UTF8Buffer getClientId() {
return connect.clientId();
}
public short getKeepAlive() {
return connect.keepAlive();
}
public UTF8Buffer getPassword() {
return connect.password();
}
public byte getType() {
return connect.messageType();
}
public UTF8Buffer getUserName() {
return connect.userName();
}
public UTF8Buffer getWillMessage() {
return connect.willMessage();
}
public QoS getWillQos() {
return connect.willQos();
}
public UTF8Buffer getWillTopic() {
return connect.willTopic();
}
public boolean isCleanSession() {
return connect.cleanSession();
}
public boolean isWillRetain() {
return connect.willRetain();
}
public void setCleanSession(boolean cleanSession) {
connect.cleanSession(cleanSession);
}
public void setClientId(String clientId) {
this.setClientId(utf8(clientId));
}
public void setClientId(UTF8Buffer clientId) {
connect.clientId(clientId);
}
public void setKeepAlive(short keepAlive) {
connect.keepAlive(keepAlive);
}
public void setPassword(String password) {
this.setPassword(utf8(password));
}
public void setPassword(UTF8Buffer password) {
connect.password(password);
}
public void setUserName(String userName) {
this.setUserName(utf8(userName));
}
public void setUserName(UTF8Buffer userName) {
connect.userName(userName);
}
public void setWillMessage(String willMessage) {
connect.willMessage(utf8(willMessage));
}
public void setWillMessage(UTF8Buffer willMessage) {
connect.willMessage(willMessage);
}
public void setWillQos(QoS willQos) {
connect.willQos(willQos);
}
public void setVersion(String version) {
if( "3.1".equals(version) ) {
connect.version(3);
} else if( "3.1.1".equals(version) ) {
connect.version(4);
}
}
public String getVersion() {
switch(connect.version()) {
case 3: return "3.1";
case 4: return "3.1.1";
default: return "unknown";
}
}
public void setWillRetain(boolean willRetain) {
connect.willRetain(willRetain);
}
public void setWillTopic(String willTopic) {
this.setWillTopic(utf8(willTopic));
}
public void setWillTopic(UTF8Buffer willTopic) {
connect.willTopic(willTopic);
}
public Executor getBlockingExecutor() {
return blockingExecutor;
}
public void setBlockingExecutor(Executor blockingExecutor) {
this.blockingExecutor = blockingExecutor;
}
public DispatchQueue getDispatchQueue() {
return dispatchQueue;
}
public void setDispatchQueue(DispatchQueue dispatchQueue) {
this.dispatchQueue = dispatchQueue;
}
public URI getLocalAddress() {
return localAddress;
}
public void setLocalAddress(String localAddress) throws URISyntaxException {
this.setLocalAddress(new URI(localAddress));
}
public void setLocalAddress(URI localAddress) {
this.localAddress = localAddress;
}
public int getMaxReadRate() {
return maxReadRate;
}
public void setMaxReadRate(int maxReadRate) {
this.maxReadRate = maxReadRate;
}
public int getMaxWriteRate() {
return maxWriteRate;
}
public void setMaxWriteRate(int maxWriteRate) {
this.maxWriteRate = maxWriteRate;
}
public int getReceiveBufferSize() {
return receiveBufferSize;
}
public void setReceiveBufferSize(int receiveBufferSize) {
this.receiveBufferSize = receiveBufferSize;
}
public URI getHost() {
return host;
}
public void setHost(String host, int port) throws URISyntaxException {
this.setHost(new URI("tcp://"+host+":"+port));
}
public void setHost(String host) throws URISyntaxException {
this.setHost(new URI(host));
}
public void setHost(URI host) {
this.host = host;
}
public int getSendBufferSize() {
return sendBufferSize;
}
public void setSendBufferSize(int sendBufferSize) {
this.sendBufferSize = sendBufferSize;
}
public SSLContext getSslContext() {
return sslContext;
}
public void setSslContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
public int getTrafficClass() {
return trafficClass;
}
public void setTrafficClass(int trafficClass) {
this.trafficClass = trafficClass;
}
public boolean isUseLocalHost() {
return useLocalHost;
}
public void setUseLocalHost(boolean useLocalHost) {
this.useLocalHost = useLocalHost;
}
public long getConnectAttemptsMax() {
return connectAttemptsMax;
}
public void setConnectAttemptsMax(long connectAttemptsMax) {
this.connectAttemptsMax = connectAttemptsMax;
}
public long getReconnectAttemptsMax() {
return reconnectAttemptsMax;
}
public void setReconnectAttemptsMax(long reconnectAttemptsMax) {
this.reconnectAttemptsMax = reconnectAttemptsMax;
}
public double getReconnectBackOffMultiplier() {
return reconnectBackOffMultiplier;
}
public void setReconnectBackOffMultiplier(double reconnectBackOffMultiplier) {
this.reconnectBackOffMultiplier = reconnectBackOffMultiplier;
}
public long getReconnectDelay() {
return reconnectDelay;
}
public void setReconnectDelay(long reconnectDelay) {
this.reconnectDelay = reconnectDelay;
}
public long getReconnectDelayMax() {
return reconnectDelayMax;
}
public void setReconnectDelayMax(long reconnectDelayMax) {
this.reconnectDelayMax = reconnectDelayMax;
}
public Tracer getTracer() {
return tracer;
}
public void setTracer(Tracer tracer) {
this.tracer = tracer;
}
}
@@ -0,0 +1,14 @@
package org.fusesource.mqtt.client;
import java.io.IOException;
import org.fusesource.mqtt.codec.CONNACK;
public class MQTTException extends IOException {
public final CONNACK connack;
public MQTTException(String msg, CONNACK connack) {
super(msg);
this.connack = connack;
}
}
@@ -0,0 +1,104 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.UTF8Buffer;
import org.fusesource.hawtdispatch.DispatchQueue;
import org.fusesource.hawtdispatch.Task;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Message {
private final UTF8Buffer topic;
private final Buffer payload;
private Callback<Callback<byte[]>> onComplete;
private final DispatchQueue queue;
boolean blocking = false;
public Message(DispatchQueue queue, UTF8Buffer topic, Buffer payload, Callback<Callback<byte[]>> onComplete) {
this.queue = queue;
this.payload = payload;
this.topic = topic;
this.onComplete = onComplete;
}
public byte[] getPayload() {
return payload.toByteArray();
}
/**
* Using getPayloadBuffer() is lower overhead version of getPayload()
* since it avoids a byte array copy.
* @return
*/
public Buffer getPayloadBuffer() {
return payload;
}
public String getTopic() {
return topic.toString();
}
/**
* Using getTopicBuffer is lower overhead version of getTopic()
* since it avoid doing UTF-8 decode.
* @return
*/
public UTF8Buffer getTopicBuffer() {
return topic;
}
public void ack() {
if( blocking ) {
final Promise<byte[]> future = new Promise<byte[]>();
ack(future);
try {
future.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
ack(null);
}
}
public void ack(final Callback<byte[]> onAcked) {
if(onComplete!=null) {
queue.execute(new Task() {
Callback<Callback<byte[]>> onCompleteCopy = onComplete;
@Override
public void run() {
onCompleteCopy.onSuccess(onAcked);
}
});
onComplete = null;
} else {
if( onAcked!=null ) {
onAcked.onSuccess(null);
}
}
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class NoopCallback<T> implements Callback<T> {
public final Callback<T> next;
public NoopCallback(Callback<T> next) {
this.next = next;
}
public void onSuccess(T value) {
if( next!=null ) {
next.onSuccess(value);
}
}
public void onFailure(Throwable value) {
if( next!=null ) {
next.onFailure(value);
}
}
}
@@ -0,0 +1,109 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Promise<T> implements Callback<T>, Future<T> {
private final CountDownLatch latch = new CountDownLatch(1);
private Callback<T> next;
private Throwable error;
private T value;
public void onFailure(Throwable value) {
Callback<T> callback = null;
synchronized(this) {
error = value;
latch.countDown();
callback = next;
}
if( callback!=null ) {
callback.onFailure(value);
}
}
public void onSuccess(T value) {
Callback<T> callback = null;
synchronized(this) {
this.value = value;
latch.countDown();
callback = next;
}
if( callback!=null ) {
callback.onSuccess(value);
}
}
public void then(Callback<T> callback) {
boolean fire = false;
synchronized(this) {
next = callback;
if( latch.getCount() == 0 ) {
fire = true;
}
}
if( fire ) {
if( error!=null ) {
callback.onFailure(error);
} else {
callback.onSuccess(value);
}
}
}
public T await(long amount, TimeUnit unit) throws Exception {
if( latch.await(amount, unit) ) {
return get();
} else {
throw new TimeoutException();
}
}
public T await() throws Exception {
latch.await();
return get();
}
private T get() throws Exception {
Throwable e = error;
if( e !=null ) {
if( e instanceof RuntimeException ) {
throw (RuntimeException) e;
} else if( e instanceof Exception) {
throw (Exception) e;
} else if( e instanceof Error) {
throw (Error) e;
} else {
// don't expect to hit this case.
throw new RuntimeException(e);
}
}
return value;
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* Function Result that carries one value.
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class ProxyCallback<T> implements Callback<T> {
public final Callback<T> next;
public ProxyCallback(Callback<T> next) {
this.next = next;
}
public void onSuccess(T value) {
if( next!=null ) {
next.onSuccess(value);
}
}
public void onFailure(Throwable value) {
if( next!=null ) {
next.onFailure(value);
}
}
}
+31
View File
@@ -0,0 +1,31 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public enum QoS {
AT_MOST_ONCE,
AT_LEAST_ONCE,
EXACTLY_ONCE
}
+78
View File
@@ -0,0 +1,78 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class Topic {
private final UTF8Buffer name;
private final QoS qos;
public Topic(String name, QoS qos) {
this(new UTF8Buffer(name), qos);
}
public Topic(UTF8Buffer name, QoS qos) {
this.name = name;
this.qos = qos;
}
public UTF8Buffer name() {
return name;
}
public QoS qos() {
return qos;
}
@Override
public boolean equals(Object o) {
if (o == null) return false;
if (o.getClass() != this.getClass()) return false;
if (this == o) return true;
Topic topic = (Topic) o;
if (name != null ? !name.equals(topic.name) : topic.name != null) return false;
if (qos != topic.qos) return false;
return true;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + (qos != null ? qos.hashCode() : 0);
return result;
}
@Override
public String toString() {
return "{ name=" + name +
", qos=" + qos +
" }";
}
}
@@ -0,0 +1,50 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.client;
import org.fusesource.mqtt.codec.MQTTFrame;
/**
* A subclass of this can be configured on an MQTT connection to
* get more insight into what it's doing.
*/
public class Tracer {
/**
* Override to log/capture debug level messages
* @param message
* @param args
*/
public void debug(String message, Object...args) {
}
/**
* Called when a MQTTFrame sent to the remote peer.
* @param frame
*/
public void onSend(MQTTFrame frame) {
}
/**
* Called when a MQTTFrame is received from the remote peer.
* @param frame
*/
public void onReceive(MQTTFrame frame) {
}
}
+103
View File
@@ -0,0 +1,103 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.hawtbuf.*;
import java.io.IOException;
import java.net.ProtocolException;
import static org.fusesource.mqtt.codec.MessageSupport.*;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class CONNACK implements Message {
public static final byte TYPE = 2;
public byte[] payload;
public static enum Code {
CONNECTION_ACCEPTED,
CONNECTION_REFUSED_UNACCEPTED_PROTOCOL_VERSION,
CONNECTION_REFUSED_IDENTIFIER_REJECTED,
CONNECTION_REFUSED_SERVER_UNAVAILABLE,
CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD,
CONNECTION_REFUSED_NOT_AUTHORIZED;
}
private Code code = Code.CONNECTION_ACCEPTED;
public byte messageType() {
return TYPE;
}
public CONNACK decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
is.skip(1);
byte c = is.readByte();
if( c >= Code.values().length ) {
throw new ProtocolException("Invalid CONNACK encoding");
}
code = Code.values()[c];
payload = new byte[is.available()];
try {
is.read(payload);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream(2);
os.writeByte(0);
os.writeByte(code.ordinal());
MQTTFrame frame = new MQTTFrame();
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
public Code code() {
return code;
}
public CONNACK code(Code code) {
this.code = code;
return this;
}
@Override
public String toString() {
return "CONNACK{" +
"code=" + code +
'}';
}
}
+284
View File
@@ -0,0 +1,284 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.io.IOException;
import java.net.ProtocolException;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class CONNECT implements MessageSupport.Message {
public static final byte TYPE = 1;
private static final UTF8Buffer V3_PROTOCOL_NAME = new UTF8Buffer("MQIsdp");
private static final UTF8Buffer V4_PROTOCOL_NAME = new UTF8Buffer("MQTT");
private short keepAlive = 30;
private UTF8Buffer clientId;
private UTF8Buffer willTopic;
private UTF8Buffer willMessage = new UTF8Buffer("");
private boolean willRetain;
private byte willQos;
private boolean cleanSession = true;
private UTF8Buffer userName;
private UTF8Buffer password;
private int version = 3;
public CONNECT(){
}
public CONNECT(CONNECT other) {
this.keepAlive = other.keepAlive;
this.clientId = other.clientId;
this.willTopic = other.willTopic;
this.willMessage = other.willMessage;
this.willRetain = other.willRetain;
this.willQos = other.willQos;
this.cleanSession = other.cleanSession;
this.userName = other.userName;
this.password = other.password;
this.version = other.version;
}
public byte messageType() {
return TYPE;
}
public CONNECT decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
UTF8Buffer protocolName = MessageSupport.readUTF(is);
if (V4_PROTOCOL_NAME.equals(protocolName)) {
version = is.readByte() & 0xFF;
if( version < 4 ) {
throw new ProtocolException("Invalid CONNECT frame: protocol name/version mismatch");
}
} else if( V3_PROTOCOL_NAME.equals(protocolName) ) {
version = is.readByte() & 0xFF;
if( version != 3 ) {
throw new ProtocolException("Invalid CONNECT frame: protocol name/version mismatch");
}
} else {
throw new ProtocolException("Invalid CONNECT frame");
}
byte flags = is.readByte();
boolean username_flag = (flags & 0x80) > 0;
boolean password_flag = (flags & 0x40) > 0;
willRetain = (flags & 0x20) > 0;
willQos = (byte) ((flags & 0x18) >>> 3);
boolean will_flag = (flags & 0x04) > 0;
cleanSession = (flags & 0x02) > 0;
keepAlive = is.readShort();
clientId = MessageSupport.readUTF(is);
if( clientId.length == 0 ) {
clientId = null;
}
if(will_flag) {
willTopic = MessageSupport.readUTF(is);
willMessage = MessageSupport.readUTF(is);
}
if( username_flag ) {
userName = MessageSupport.readUTF(is);
}
if( password_flag ) {
password = MessageSupport.readUTF(is);
}
return this;
}
public MQTTFrame encode() {
try {
if( (clientId==null || clientId.length == 0) && !cleanSession ) {
throw new IllegalArgumentException("A clean session must be used when no clientId is specified");
}
DataByteArrayOutputStream os = new DataByteArrayOutputStream(500);
if(version==3) {
MessageSupport.writeUTF(os, V3_PROTOCOL_NAME);
os.writeByte(version);
} else if(version >= 4) {
MessageSupport.writeUTF(os, V4_PROTOCOL_NAME);
os.writeByte(version);
} else {
throw new IllegalArgumentException("Invalid version: "+version);
}
int flags = 0;
if(userName!=null) {
flags |= 0x80;
}
if(password!=null) {
flags |= 0x40;
}
if(willTopic!=null && willMessage!=null) {
flags |= 0x04;
if(willRetain) {
flags |= 0x20;
}
flags |= (willQos << 3) & 0x18;
}
if(cleanSession) {
flags |= 0x02;
}
os.writeByte(flags);
os.writeShort(keepAlive);
MessageSupport.writeUTF(os, clientId);
if(willTopic!=null && willMessage!=null) {
MessageSupport.writeUTF(os, willTopic);
MessageSupport.writeUTF(os, willMessage);
}
if(userName!=null) {
MessageSupport.writeUTF(os, userName);
}
if(password!=null) {
MessageSupport.writeUTF(os, password);
}
MQTTFrame frame = new MQTTFrame();
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
public boolean cleanSession() {
return cleanSession;
}
public CONNECT cleanSession(boolean cleanSession) {
this.cleanSession = cleanSession;
return this;
}
public UTF8Buffer clientId() {
return clientId;
}
public CONNECT clientId(UTF8Buffer clientId) {
this.clientId = clientId;
return this;
}
public short keepAlive() {
return keepAlive;
}
public CONNECT keepAlive(short keepAlive) {
this.keepAlive = keepAlive;
return this;
}
public UTF8Buffer password() {
return password;
}
public CONNECT password(UTF8Buffer password) {
this.password = password;
return this;
}
public UTF8Buffer userName() {
return userName;
}
public CONNECT userName(UTF8Buffer userName) {
this.userName = userName;
return this;
}
public UTF8Buffer willMessage() {
return willMessage;
}
public CONNECT willMessage(UTF8Buffer willMessage) {
this.willMessage = willMessage;
return this;
}
public QoS willQos() {
return QoS.values()[willQos];
}
public CONNECT willQos(QoS willQos) {
this.willQos = (byte) willQos.ordinal();
return this;
}
public boolean willRetain() {
return willRetain;
}
public CONNECT willRetain(boolean willRetain) {
this.willRetain = willRetain;
return this;
}
public UTF8Buffer willTopic() {
return willTopic;
}
public CONNECT willTopic(UTF8Buffer willTopic) {
this.willTopic = willTopic;
return this;
}
public int version() {
return version;
}
public CONNECT version(int version) {
if(version==3) {
this.version = version;
} else if(version >= 4) {
this.version = version;
} else {
throw new IllegalArgumentException("Invalid version: "+version);
}
return this;
}
@Override
public String toString() {
return "CONNECT{" +
"cleanSession=" + cleanSession +
", keepAlive=" + keepAlive +
", clientId=" + clientId +
", willTopic=" + willTopic +
", willMessage=" + willMessage +
", willRetain=" + willRetain +
", willQos=" + willQos +
", userName=" + userName +
", password=" + password +
'}';
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class DISCONNECT extends MessageSupport.EmptyBase implements MessageSupport.Message {
public static final byte TYPE = 14;
public byte messageType() {
return TYPE;
}
@Override
public DISCONNECT decode(MQTTFrame frame) throws ProtocolException {
return (DISCONNECT) super.decode(frame);
}
@Override
public String toString() {
return "DISCONNECT";
}
}
@@ -0,0 +1,159 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class MQTTFrame extends MessageSupport.HeaderBase {
private static final Buffer[] NO_BUFFERS = new Buffer[0];
public Buffer[] buffers = NO_BUFFERS;
public MQTTFrame() {
}
public MQTTFrame( Buffer buffer) {
this(new Buffer[]{buffer});
}
public MQTTFrame( Buffer[] buffers) {
this.buffers = buffers;
}
public Buffer[] buffers() {
return buffers;
}
public MQTTFrame buffers(Buffer...buffers) {
this.buffers = buffers;
return this;
}
public MQTTFrame buffer(Buffer buffer) {
this.buffers = new Buffer[]{buffer};
return this;
}
@Override
public byte header() {
return super.header();
}
@Override
public MQTTFrame header(byte header) {
return (MQTTFrame)super.header(header);
}
@Override
public byte messageType() {
return super.messageType();
}
@Override
public MQTTFrame commandType(int type) {
return (MQTTFrame)super.commandType(type);
}
@Override
public boolean dup() {
return super.dup();
}
@Override
public MQTTFrame dup(boolean dup) {
return (MQTTFrame) super.dup(dup);
}
@Override
public QoS qos() {
return super.qos();
}
@Override
public MQTTFrame qos(QoS qos) {
return (MQTTFrame) super.qos(qos);
}
@Override
public boolean retain() {
return super.retain();
}
@Override
public MQTTFrame retain(boolean retain) {
return (MQTTFrame) super.retain(retain);
}
@Override
public String toString() {
String type = "unknown";
switch(messageType()) {
case CONNECT.TYPE:
type = "CONNECT";
break;
case CONNACK.TYPE:
type = "CONNACK";
break;
case DISCONNECT.TYPE:
type = "DISCONNECT";
break;
case PINGREQ.TYPE:
type = "PINGREQ";
break;
case PINGRESP.TYPE:
type = "PINGRESP";
break;
case SUBSCRIBE.TYPE:
type = "SUBSCRIBE";
break;
case UNSUBSCRIBE.TYPE:
type = "UNSUBSCRIBE";
break;
case UNSUBACK.TYPE:
type = "UNSUBACK";
break;
case PUBLISH.TYPE:
type = "PUBLISH";
break;
case SUBACK.TYPE:
type = "SUBACK";
break;
case PUBACK.TYPE:
type = "PUBACK";
break;
case PUBREC.TYPE:
type = "PUBREC";
break;
case PUBREL.TYPE:
type = "PUBREL";
break;
case PUBCOMP.TYPE:
type = "PUBCOMP";
break;
default:
}
return "MQTTFrame { type: "+type+", qos: "+qos()+", dup:"+dup()+" }";
}
}
@@ -0,0 +1,135 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtdispatch.transport.AbstractProtocolCodec;
import org.fusesource.hawtdispatch.util.BufferPools;
import java.io.IOException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class MQTTProtocolCodec extends AbstractProtocolCodec {
private static final BufferPools BUFFER_POOLS = new BufferPools();
private int maxMessageLength = 1024*1024*100;
public MQTTProtocolCodec() {
this.bufferPools = BUFFER_POOLS;
}
public int getMaxMessageLength() {
return maxMessageLength;
}
public void setMaxMessageLength(int maxMessageLength) {
this.maxMessageLength = maxMessageLength;
}
@Override
protected void encode(Object value) throws IOException {
MQTTFrame frame = (MQTTFrame) value;
nextWriteBuffer.write(frame.header());
int remaining = 0;
for(Buffer buffer : frame.buffers) {
remaining += buffer.length;
}
do {
byte digit = (byte) (remaining & 0x7F);
remaining >>>= 7;
if (remaining > 0) {
digit |= 0x80;
}
nextWriteBuffer.write(digit);
} while (remaining > 0);
for(Buffer buffer : frame.buffers) {
nextWriteBuffer.write(buffer.data, buffer.offset, buffer.length);
}
}
@Override
protected Action initialDecodeAction() {
return readHeader;
}
private final Action readHeader = new Action() {
public MQTTFrame apply() throws IOException {
int length = readLength();
if( length >= 0 ) {
if( length > maxMessageLength) {
throw new IOException("The maximum message length was exceeded");
}
byte header = readBuffer.get(readStart);
readStart = readEnd;
if( length > 0 ) {
nextDecodeAction = readBody(header, length);
} else {
return new MQTTFrame().header(header);
}
}
return null;
}
};
private int readLength() throws IOException {
readEnd = readStart+2; // Header is at least 2 bytes..
int limit = readBuffer.position();
int length = 0;
int multiplier = 1;
byte digit;
while (readEnd-1 < limit) {
// last byte is part of the encoded length..
digit = readBuffer.get(readEnd-1);
length += (digit & 0x7F) * multiplier;
if( (digit & 0x80) == 0 ) {
return length;
}
// length extends out one more byte..
multiplier <<= 7;
readEnd++;
}
return -1;
}
Action readBody(final byte header, final int length) {
return new Action() {
public MQTTFrame apply() throws IOException {
int limit = readBuffer.position();
if ((limit - readStart) < length) {
readEnd = limit;
return null;
} else {
Buffer body = new Buffer(readBuffer.array(), readStart, length);
readEnd = readStart = readStart + length;
nextDecodeAction = readHeader;
return new MQTTFrame(body).header(header);
}
}
};
}
}
@@ -0,0 +1,202 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.io.IOException;
import java.net.ProtocolException;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import org.fusesource.hawtbuf.UTF8Buffer;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public final class MessageSupport {
private MessageSupport() throws InstantiationException {
throw new InstantiationException("This class is not for instantiation");
}
/**
* All command objects implement this interface.
*/
static public interface Message {
public byte messageType();
public Message decode(MQTTFrame frame) throws ProtocolException;
public MQTTFrame encode();
}
/**
* All command objects that can get acked implement this interface.
*/
static public interface Acked extends Message {
public boolean dup();
public Acked dup(boolean dup);
public QoS qos();
public short messageId();
public Acked messageId(short messageId);
}
static protected UTF8Buffer readUTF(DataByteArrayInputStream is) throws ProtocolException {
int size = is.readUnsignedShort();
Buffer buffer = is.readBuffer(size);
if (buffer == null || buffer.length != size) {
throw new ProtocolException("Invalid message encoding");
}
return buffer.utf8();
}
static protected void writeUTF(DataByteArrayOutputStream os, Buffer buffer) throws IOException {
os.writeShort(buffer.length);
os.write(buffer);
}
static abstract public class AckBase {
private short messageId;
public byte[] payload;
abstract byte messageType();
protected AckBase decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
messageId = is.readShort();
payload = new byte[is.available()];
try {
is.read(payload);
} catch (IOException e) {
e.printStackTrace();
}
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream(2);
os.writeShort(messageId);
MQTTFrame frame = new MQTTFrame();
frame.commandType(messageType());
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
public short messageId() {
return messageId;
}
protected AckBase messageId(short messageId) {
this.messageId = messageId;
return this;
}
@Override
public String toString() {
return getClass().getSimpleName()+"{" +
"messageId=" + messageId +
'}';
}
}
static abstract public class EmptyBase {
abstract byte messageType();
protected EmptyBase decode(MQTTFrame frame) throws ProtocolException {
return this;
}
public MQTTFrame encode() {
return new MQTTFrame().commandType(messageType());
}
}
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
static public class HeaderBase {
protected byte header;
protected byte header() {
return header;
}
protected HeaderBase header(byte header) {
this.header = header;
return this;
}
protected byte messageType() {
return (byte) ((header & 0xF0) >>> 4);
}
protected HeaderBase commandType(int type) {
this.header &= 0x0F;
this.header |= (type << 4) & 0xF0;
return this;
}
protected QoS qos() {
return QoS.values()[((header & 0x06) >>> 1)];
}
protected HeaderBase qos(QoS qos) {
this.header &= 0xF9;
this.header |= (qos.ordinal() << 1) & 0x06;
return this;
}
protected boolean dup() {
return (header & 0x08) > 0;
}
protected HeaderBase dup(boolean dup) {
if(dup) {
this.header |= 0x08;
} else {
this.header &= 0xF7;
}
return this;
}
protected boolean retain() {
return (header & 0x01) > 0;
}
protected HeaderBase retain(boolean retain) {
if(retain) {
this.header |= 0x01;
} else {
this.header &= 0xFE;
}
return this;
}
}
}
@@ -0,0 +1,48 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
import static org.fusesource.mqtt.codec.MessageSupport.*;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PINGREQ extends MessageSupport.EmptyBase implements Message {
public static final byte TYPE = 12;
public byte messageType() {
return TYPE;
}
@Override
public PINGREQ decode(MQTTFrame frame) throws ProtocolException {
return (PINGREQ) super.decode(frame);
}
@Override
public String toString() {
return "PINGREQ";
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PINGRESP extends MessageSupport.EmptyBase implements MessageSupport.Message {
public static final byte TYPE = 13;
public byte messageType() {
return TYPE;
}
@Override
public PINGRESP decode(MQTTFrame frame) throws ProtocolException {
return (PINGRESP) super.decode(frame);
}
@Override
public String toString() {
return "PINGRESP";
}
}
+46
View File
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PUBACK extends MessageSupport.AckBase implements MessageSupport.Message {
public static final byte TYPE = 4;
public byte messageType() {
return TYPE;
}
@Override
public PUBACK decode(MQTTFrame frame) throws ProtocolException {
return (PUBACK) super.decode(frame);
}
@Override
public PUBACK messageId(short messageId) {
return (PUBACK) super.messageId(messageId);
}
}
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PUBCOMP extends MessageSupport.AckBase implements MessageSupport.Message {
public static final byte TYPE = 7;
public byte messageType() {
return TYPE;
}
@Override
public PUBCOMP decode(MQTTFrame frame) throws ProtocolException {
return (PUBCOMP) super.decode(frame);
}
@Override
public PUBCOMP messageId(short messageId) {
return (PUBCOMP) super.messageId(messageId);
}
}
+159
View File
@@ -0,0 +1,159 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.Buffer;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import org.fusesource.hawtbuf.UTF8Buffer;
import java.io.IOException;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PUBLISH extends MessageSupport.HeaderBase implements MessageSupport.Message, MessageSupport.Acked {
public static final byte TYPE = 3;
private UTF8Buffer topicName;
private short messageId;
private Buffer payload;
public PUBLISH() {
qos(QoS.AT_LEAST_ONCE);
}
public byte messageType() {
return TYPE;
}
public PUBLISH decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
header(frame.header());
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
topicName = MessageSupport.readUTF(is);
QoS qos = qos();
if(qos != QoS.AT_MOST_ONCE) {
messageId = is.readShort();
}
payload = is.readBuffer(is.available());
if( payload == null ) {
payload = new Buffer(0);
}
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream();
MessageSupport.writeUTF(os, topicName);
QoS qos = qos();
if(qos != QoS.AT_MOST_ONCE) {
os.writeShort(messageId);
}
MQTTFrame frame = new MQTTFrame();
frame.header(header());
frame.commandType(TYPE);
if(payload!=null && payload.length!=0) {
os.write(payload);
}
frame.buffer(os.toBuffer());
return frame;
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
@Override
public boolean dup() {
return super.dup();
}
@Override
public PUBLISH dup(boolean dup) {
return (PUBLISH) super.dup(dup);
}
@Override
public QoS qos() {
return super.qos();
}
@Override
public PUBLISH qos(QoS qos) {
return (PUBLISH) super.qos(qos);
}
@Override
public boolean retain() {
return super.retain();
}
@Override
public PUBLISH retain(boolean retain) {
return (PUBLISH) super.retain(retain);
}
public short messageId() {
return messageId;
}
public PUBLISH messageId(short messageId) {
this.messageId = messageId;
return this;
}
public Buffer payload() {
return payload;
}
public PUBLISH payload(Buffer payload) {
this.payload = payload;
return this;
}
public UTF8Buffer topicName() {
return topicName;
}
public PUBLISH topicName(UTF8Buffer topicName) {
this.topicName = topicName;
return this;
}
@Override
public String toString() {
return "PUBLISH{" +
"dup=" + dup() +
", qos=" + qos() +
", retain=" + retain() +
", messageId=" + messageId +
", topicName=" + topicName +
", payload=" + payload +
'}';
}
}
+47
View File
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PUBREC extends MessageSupport.AckBase implements MessageSupport.Message {
public static final byte TYPE = 5;
public byte messageType() {
return TYPE;
}
@Override
public PUBREC decode(MQTTFrame frame) throws ProtocolException {
return (PUBREC) super.decode(frame);
}
@Override
public PUBREC messageId(short messageId) {
return (PUBREC) super.messageId(messageId);
}
}
+103
View File
@@ -0,0 +1,103 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import java.io.IOException;
import java.net.ProtocolException;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class PUBREL extends MessageSupport.HeaderBase implements MessageSupport.Message, MessageSupport.Acked {
public static final byte TYPE = 6;
private short messageId;
public byte messageType() {
return TYPE;
}
public PUBREL() {
qos(QoS.AT_LEAST_ONCE);
}
public PUBREL decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
header(frame.header());
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
messageId = is.readShort();
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream(2);
os.writeShort(messageId);
MQTTFrame frame = new MQTTFrame();
frame.header(header());
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
@Override
public boolean dup() {
return super.dup();
}
@Override
public PUBREL dup(boolean dup) {
return (PUBREL) super.dup(dup);
}
@Override
public QoS qos() {
return super.qos();
}
public short messageId() {
return messageId;
}
public PUBREL messageId(short messageId) {
this.messageId = messageId;
return this;
}
@Override
public String toString() {
return "PUBREL{" +
"dup=" + dup() +
", qos=" + qos() +
", messageId=" + messageId +
'}';
}
}
+94
View File
@@ -0,0 +1,94 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.Arrays;
import static org.fusesource.mqtt.codec.MessageSupport.*;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class SUBACK implements Message {
public static final byte[] NO_GRANTED_QOS = new byte[0];
public static final byte TYPE = 9;
private short messageId;
private byte[] grantedQos = NO_GRANTED_QOS;
public byte messageType() {
return TYPE;
}
public SUBACK decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
messageId = is.readShort();
grantedQos = is.readBuffer(is.available()).toByteArray();
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream(2+grantedQos.length);
os.writeShort(messageId);
os.write(grantedQos);
MQTTFrame frame = new MQTTFrame();
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
public byte[] grantedQos() {
return grantedQos;
}
public SUBACK grantedQos(byte[] grantedQos) {
this.grantedQos = grantedQos;
return this;
}
public short messageId() {
return messageId;
}
public SUBACK messageId(short messageId) {
this.messageId = messageId;
return this;
}
@Override
public String toString() {
return "SUBACK{" +
"grantedQos=" + Arrays.toString(grantedQos) +
", messageId=" +messageId +
'}';
}
}
@@ -0,0 +1,134 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.mqtt.client.Topic;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class SUBSCRIBE extends MessageSupport.HeaderBase implements MessageSupport.Message, MessageSupport.Acked {
public static final byte TYPE = 8;
public static final Topic[] NO_TOPICS = new Topic[0];
private short messageId;
private Topic[] topics = NO_TOPICS;
public SUBSCRIBE() {
qos(QoS.AT_LEAST_ONCE);
}
public byte messageType() {
return TYPE;
}
public SUBSCRIBE decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
header(frame.header());
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
QoS qos = qos();
if(qos != QoS.AT_MOST_ONCE) {
messageId = is.readShort();
}
ArrayList<Topic> list = new ArrayList<Topic>();
while(is.available() > 0) {
Topic topic = new Topic(MessageSupport.readUTF(is), QoS.values()[is.readByte()]);
list.add(topic);
}
topics = list.toArray(new Topic[list.size()]);
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream();
QoS qos = qos();
if(qos != QoS.AT_MOST_ONCE) {
os.writeShort(messageId);
}
for(Topic topic: topics) {
MessageSupport.writeUTF(os, topic.name());
os.writeByte(topic.qos().ordinal());
}
MQTTFrame frame = new MQTTFrame();
frame.header(header());
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
@Override
public boolean dup() {
return super.dup();
}
@Override
public SUBSCRIBE dup(boolean dup) {
return (SUBSCRIBE) super.dup(dup);
}
@Override
public QoS qos() {
return super.qos();
}
public short messageId() {
return messageId;
}
public SUBSCRIBE messageId(short messageId) {
this.messageId = messageId;
return this;
}
public Topic[] topics() {
return topics;
}
public SUBSCRIBE topics(Topic[] topics) {
this.topics = topics;
return this;
}
@Override
public String toString() {
return "SUBSCRIBE{" +
"dup=" + dup() +
", qos=" + qos() +
", messageId=" + messageId +
", topics=" + (topics == null ? null : Arrays.asList(topics)) +
'}';
}
}
@@ -0,0 +1,49 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import java.net.ProtocolException;
import static org.fusesource.mqtt.codec.MessageSupport.AckBase;
import static org.fusesource.mqtt.codec.MessageSupport.*;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class UNSUBACK extends AckBase implements Message {
public static final byte TYPE = 11;
public byte messageType() {
return TYPE;
}
@Override
public UNSUBACK decode(MQTTFrame frame) throws ProtocolException {
return (UNSUBACK) super.decode(frame);
}
@Override
public UNSUBACK messageId(short messageId) {
return (UNSUBACK) super.messageId(messageId);
}
}
@@ -0,0 +1,130 @@
/**
* Copyright (C) 2010-2012, FuseSource Corp. All rights reserved.
*
* http://fusesource.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.mqtt.codec;
import org.fusesource.mqtt.client.QoS;
import org.fusesource.hawtbuf.DataByteArrayInputStream;
import org.fusesource.hawtbuf.DataByteArrayOutputStream;
import org.fusesource.hawtbuf.UTF8Buffer;
import java.io.IOException;
import java.net.ProtocolException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* <p>
* </p>
*
* @author <a href="http://hiramchirino.com">Hiram Chirino</a>
*/
public class UNSUBSCRIBE extends MessageSupport.HeaderBase implements MessageSupport.Message, MessageSupport.Acked {
public static final byte TYPE = 10;
public static final UTF8Buffer[] NO_TOPICS = new UTF8Buffer[0];
private short messageId;
private UTF8Buffer[] topics = NO_TOPICS;
public UNSUBSCRIBE() {
qos(QoS.AT_LEAST_ONCE);
}
public byte messageType() {
return TYPE;
}
public UNSUBSCRIBE decode(MQTTFrame frame) throws ProtocolException {
assert(frame.buffers.length == 1);
header(frame.header());
DataByteArrayInputStream is = new DataByteArrayInputStream(frame.buffers[0]);
messageId = is.readShort();
ArrayList<UTF8Buffer> list = new ArrayList<UTF8Buffer>();
while(is.available() > 0) {
list.add(MessageSupport.readUTF(is));
}
topics = list.toArray(new UTF8Buffer[list.size()]);
return this;
}
public MQTTFrame encode() {
try {
DataByteArrayOutputStream os = new DataByteArrayOutputStream();
QoS qos = qos();
if(qos != QoS.AT_MOST_ONCE) {
os.writeShort(messageId);
}
for(UTF8Buffer topic: topics) {
MessageSupport.writeUTF(os, topic);
}
MQTTFrame frame = new MQTTFrame();
frame.header(header());
frame.commandType(TYPE);
return frame.buffer(os.toBuffer());
} catch (IOException e) {
throw new RuntimeException("The impossible happened");
}
}
@Override
public boolean dup() {
return super.dup();
}
@Override
public UNSUBSCRIBE dup(boolean dup) {
return (UNSUBSCRIBE) super.dup(dup);
}
@Override
public QoS qos() {
return super.qos();
}
public short messageId() {
return messageId;
}
public UNSUBSCRIBE messageId(short messageId) {
this.messageId = messageId;
return this;
}
public UTF8Buffer[] topics() {
return topics;
}
public UNSUBSCRIBE topics(UTF8Buffer[] topics) {
this.topics = topics;
return this;
}
@Override
public String toString() {
return "UNSUBSCRIBE{" +
"dup=" + dup() +
", qos=" + qos() +
", messageId=" + messageId +
", topics=" + (topics == null ? null : Arrays.asList(topics)) +
'}';
}
}