wildfire chat server source initial submit
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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 +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@@ -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)) +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user