From 71aa63ec1a06f392498fbd97537e0295ff39e51c Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 11:03:27 +0800 Subject: [PATCH 01/67] First commit --- .../rocketmq/mqtt/cs/config/ConnectConf.java | 11 +++++++ .../rocketmq/mqtt/cs/starter/MqttServer.java | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java index 904b66d55..aab2a1f94 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java @@ -49,6 +49,9 @@ public class ConnectConf { private int mqttWsPort = 8888; private int quicPort = 14567; + + private int coapPort = 5683; + private boolean enableQuic = false; private boolean enableTlsSever = false; @@ -126,6 +129,14 @@ public int getMqttWsPort() { return mqttWsPort; } + public int getCoapPort() { + return coapPort; + } + + public void setCoapPort(int coapPort) { + this.coapPort = coapPort; + } + public boolean isEnableTlsSever() { return enableTlsSever; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index f68cd57c1..3714c7362 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -28,6 +28,7 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.WriteBufferWaterMark; import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioDatagramChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; @@ -72,6 +73,8 @@ public class MqttServer { private final ServerBootstrap wsServerBootstrap = new ServerBootstrap(); private final ServerBootstrap tlsServerBootstrap = new ServerBootstrap(); + private Bootstrap coapBootstrap = new Bootstrap(); + private final Bootstrap quicBootstrap = new Bootstrap(); @Resource @@ -113,6 +116,8 @@ public void init() throws Exception { startWs(); + startCoap(); + // QUIC over DTLS if (connectConf.isEnableQuic()) { startQuic(); @@ -291,4 +296,28 @@ protected void initChannel(QuicStreamChannel ch) { LOGGER.info("MQTT server for QUIC over DTLS started, listening: {}", connectConf.getQuicPort()); } + private void startCoap() { + int port = connectConf.getCoapPort(); + coapBootstrap + .group(new NioEventLoopGroup(connectConf.getNettyWorkerThreadNum())) + .channel(NioDatagramChannel.class) + .option(ChannelOption.SO_BROADCAST, true) + .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .option(ChannelOption.WRITE_BUFFER_WATER_MARK,new WriteBufferWaterMark(connectConf.getLowWater(), connectConf.getHighWater())) + .localAddress(new InetSocketAddress(port)) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(DatagramChannel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); +// pipeline.addLast("coap-handler", new CoapHandler()); +// pipeline.addLast("coap-encoder", new CoapEncoder()); +// pipeline.addLast("coap-decoder", new CoapDecoder()); +// pipeline.addLast("coap-dispatcher", coapPacketDispatcher); + } + }); + coapBootstrap.bind(); + LOGGER.info("start coap server , port:{}", port); + + } + } From 1f94e76f781ccfc11fb5fd6741cc3813b177559e Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 13:42:00 +0800 Subject: [PATCH 02/67] Add basic coap message definitions --- .../mqtt/common/model/CoapMessageCode.java | 90 +++++++++++++++++++ .../common/model/CoapMessageOptionNumber.java | 75 ++++++++++++++++ .../mqtt/common/model/CoapMessageType.java | 57 ++++++++++++ .../rocketmq/mqtt/common/model/Constants.java | 4 + 4 files changed, 226 insertions(+) create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageType.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java new file mode 100644 index 000000000..c0d93a04d --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +public enum CoapMessageCode { + // Request Code, 0.xx + GET(1), + POST(2), + PUT(3), + DELETE(4), + + // Response Success Code, 2.xx + CREATED(65), + DELETED(66), + Valid(67), + CHANGED(68), + CONTENT(69), + + // Response Client Error Code, 4.xx + BAD_REQUEST(128), + UNAUTHORIZED(129), + BAD_OPTION(130), + FORBIDDEN(131), + NOT_FOUND(132), + METHOD_NOT_ALLOWED(133), + NOT_ACCEPTABLE(134), + PRECONDITION_FAILED(140), + REQUEST_ENTITY_TOO_LARGE(141), + UNSUPPORTED_CONTENT_FORMAT(143), + + // Response Server Error Code, 5.xx + INTERNAL_SERVER_ERROR(160), + NOT_IMPLEMENTED(161), + BAD_GATEWAY(162), + SERVICE_UNAVAILABLE(163), + GATEWAY_TIMEOUT(164), + PROXYING_NOT_SUPPORTED(165); + + private static final CoapMessageCode[] VALUES; + private final int value; + + private CoapMessageCode(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + public static CoapMessageCode valueOf(int code) { + if (code > 0 && code < VALUES.length && VALUES[code] != null) { + return VALUES[code]; + } else { + throw new IllegalArgumentException("Unknown CoapMessageCode " + code); + } + } + + public static boolean isRequestCode(CoapMessageCode code) { + return (code == GET) || (code == POST) || (code == PUT) || (code == DELETE); + } + + static { + CoapMessageCode[] values = values(); + VALUES = new CoapMessageCode[192]; // Using 192 since the highest defined code is 192 + + for (CoapMessageCode code : values) { + int value = code.value; + if (VALUES[value] != null) { + throw new AssertionError("Value already in use: " + value + " by " + VALUES[value]); + } + VALUES[value] = code; + } + } + + +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java new file mode 100644 index 000000000..f6d46c566 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +public enum CoapMessageOptionNumber { + IF_MATCH(1), + URI_HOST(3), + ETAG(4), + IF_NONE_MATCH(5), + OBSERVE(6), + URI_PORT(7), + LOCATION_PATH(8), + URI_PATH(11), + CONTENT_FORMAT(12), + MAX_AGE(14), + URI_QUERY(15), + ACCEPT(17), + LOCATION_QUERY(20), + BLOCK_2(23), + BLOCK_1(27), + SIZE_2(28), + PROXY_URI(35), + PROXY_SCHEME(39), + SIZE_1(60); + + private static final CoapMessageOptionNumber[] VALUES; + private final int value; + + private CoapMessageOptionNumber(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + public static boolean isValid(int number) { + return number > 0 && number < VALUES.length && VALUES[number] != null; + } + + public static CoapMessageOptionNumber valueOf(int number) { + if (number > 0 && number < VALUES.length && VALUES[number] != null) { + return VALUES[number]; + } else { + throw new IllegalArgumentException("Unknown CoapMessageOptionNumber " + number); + } + } + + static { + CoapMessageOptionNumber[] values = values(); + VALUES = new CoapMessageOptionNumber[192]; // Using 192 since the highest defined code is 192 + + for (CoapMessageOptionNumber number : values) { + int value = number.value; + if (VALUES[value] != null) { + throw new AssertionError("Value already in use: " + value + " by " + VALUES[value]); + } + VALUES[value] = number; + } + } +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageType.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageType.java new file mode 100644 index 000000000..744942238 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageType.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +public enum CoapMessageType { + CON(0), + NON(1), + ACK(2), + RST(3); + + private static final CoapMessageType[] VALUES; + private final int value; + + private CoapMessageType(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + public static CoapMessageType valueOf(int type) { + if (type >= 0 && type < VALUES.length) { + return VALUES[type]; + } else { + throw new IllegalArgumentException("Unknown CoapMessageType " + type); + } + } + + static { + CoapMessageType[] values = values(); + VALUES = new CoapMessageType[values.length + 1]; + + for (CoapMessageType type : values) { + int value = type.value; + if (VALUES[value] != null) { + throw new AssertionError("Value already in use: " + value + " by " + VALUES[value]); + } + VALUES[value] = type; + } + } + +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java index abc86d3b2..948616e29 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java @@ -73,4 +73,8 @@ public class Constants { public static final String CLIENT_EVENT_ORIGIN_TOPIC = MQTT_SYSTEM_TOPIC + MQTT_TOPIC_DELIMITER + CLIENT_EVENT_SECOND_TOPIC; public static final int CLIENT_EVENT_BATCH_SIZE = 100; + + public static final int COAP_VERSION = 1; + public static final int COAP_PAYLOAD_MARKER = 0xFF; + public static final int COAP_MAX_TOKEN_LENGTH = 8; } From 31a96e5b5d2719bebf91355510b36909b249498d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 15:19:28 +0800 Subject: [PATCH 03/67] Complete CoapDecoder --- distribution/conf/meta.conf | 4 +- distribution/conf/service.conf | 12 +- .../mqtt/common/model/CoapMessage.java | 146 +++++++ .../mqtt/common/model/CoapMessageOption.java | 47 +++ .../common/model/CoapMessageOptionNumber.java | 4 +- .../mqtt/common/model/CoapRequestMessage.java | 81 ++++ .../mqtt/common/model/CoapRequestType.java | 59 +++ .../rocketmq/mqtt/common/model/Constants.java | 8 + .../mqtt/cs/protocol/coap/CoapDecoder.java | 358 ++++++++++++++++++ 9 files changed, 709 insertions(+), 10 deletions(-) create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOption.java create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java diff --git a/distribution/conf/meta.conf b/distribution/conf/meta.conf index d4ee41852..2774315b6 100644 --- a/distribution/conf/meta.conf +++ b/distribution/conf/meta.conf @@ -14,5 +14,5 @@ # limitations under the License. -selfAddress= -membersAddress= +selfAddress=11.159.23.108:25000 +membersAddress=11.159.23.108:25000,11.159.23.111:25000,11.159.23.104:25000 diff --git a/distribution/conf/service.conf b/distribution/conf/service.conf index 7ac8f51a3..767070344 100644 --- a/distribution/conf/service.conf +++ b/distribution/conf/service.conf @@ -14,11 +14,11 @@ # limitations under the License. -username= -secretKey= +username=test +secretKey=test -NAMESRV_ADDR= -eventNotifyRetryTopic= -clientRetryTopic= +NAMESRV_ADDR=11.159.23.108:9876 +eventNotifyRetryTopic=eventNotifyRetryTopic +clientRetryTopic=clientRetryTopic -metaAddr= \ No newline at end of file +metaAddr=11.159.23.108:25000,11.159.23.111:25000,11.159.23.104:25000 diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java new file mode 100644 index 000000000..054830726 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class CoapMessage { + private int version; + private CoapMessageType type; + private int tokenLength; + private CoapMessageCode code; + private int messageId; + private byte[] token; + private List options = new ArrayList<>(); + private byte[] payload; + private InetSocketAddress remoteAddress; + + public CoapMessage(int version, CoapMessageType type, int tokenLength, CoapMessageCode code, int messageId, byte[] token, byte[] payload, InetSocketAddress remoteAddress) { + this.version = version; + this.type = type; + this.tokenLength = tokenLength; + this.code = code; + this.messageId = messageId; + this.token = token; + this.payload = payload; + this.remoteAddress = remoteAddress; + } + + public CoapMessage(int version, CoapMessageType type, int tokenLength, CoapMessageCode code, int messageId, byte[] token, InetSocketAddress remoteAddress) { + this.version = version; + this.type = type; + this.tokenLength = tokenLength; + this.code = code; + this.messageId = messageId; + this.token = token; + this.remoteAddress = remoteAddress; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } + + public CoapMessageType getType() { + return type; + } + + public void setType(CoapMessageType type) { + this.type = type; + } + + public int getTokenLength() { + return tokenLength; + } + + public void setTokenLength(int tokenLength) { + this.tokenLength = tokenLength; + } + + public CoapMessageCode getCode() { + return code; + } + + public void setCode(CoapMessageCode code) { + this.code = code; + } + + public int getMessageId() { + return messageId; + } + + public void setMessageId(int messageId) { + this.messageId = messageId; + } + + public byte[] getToken() { + return token; + } + + public void setToken(byte[] token) { + this.token = token; + } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } + + public void addOption(CoapMessageOption option) { + this.options.add(option); + } + + public byte[] getPayload() { + return payload; + } + + public void setPayload(byte[] payload) { + this.payload = payload; + } + + public InetSocketAddress getRemoteAddress() { + return remoteAddress; + } + + public void setRemoteAddress(InetSocketAddress remoteAddress) { + this.remoteAddress = remoteAddress; + } + + @Override + public String toString() { + return "CoapMessage{" + + "version=" + version + + ", type=" + type + + ", tokenLength=" + tokenLength + + ", code=" + code + + ", messageId=" + messageId + + ", token=" + Arrays.toString(token) + + ", options=" + options + + ", payload=" + Arrays.toString(payload) + + ", remoteAddress=" + remoteAddress + + '}'; + } +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOption.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOption.java new file mode 100644 index 000000000..3d38a8a8d --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOption.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +public class CoapMessageOption { + private CoapMessageOptionNumber optionNumber; + private byte[] optionValue; + + public CoapMessageOption(CoapMessageOptionNumber optionNumber, byte[] optionValue) { + this.optionNumber = optionNumber; + this.optionValue = optionValue; + } + + public CoapMessageOption(int optionNumber, byte[] optionValue) { + this(CoapMessageOptionNumber.valueOf(optionNumber), optionValue); + } + + public CoapMessageOptionNumber getOptionNumber() { + return optionNumber; + } + + public void setOptionNumber(CoapMessageOptionNumber optionNumber) { + this.optionNumber = optionNumber; + } + + public byte[] getOptionValue() { + return optionValue; + } + + public void setOptionValue(byte[] optionValue) { + this.optionValue = optionValue; + } +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java index f6d46c566..b691b7399 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java @@ -49,11 +49,11 @@ public int value() { } public static boolean isValid(int number) { - return number > 0 && number < VALUES.length && VALUES[number] != null; + return number > 0 && number < 192 && VALUES[number] != null; } public static CoapMessageOptionNumber valueOf(int number) { - if (number > 0 && number < VALUES.length && VALUES[number] != null) { + if (number > 0 && number < 192 && VALUES[number] != null) { return VALUES[number]; } else { throw new IllegalArgumentException("Unknown CoapMessageOptionNumber " + number); diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java new file mode 100644 index 000000000..5b5fc1824 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java @@ -0,0 +1,81 @@ +package org.apache.rocketmq.mqtt.common.model; + +import io.netty.handler.codec.mqtt.MqttQoS; + +import java.net.InetSocketAddress; + +public class CoapRequestMessage extends CoapMessage { + + private CoapRequestType requestType; + private String topic; + private String clientId; + private MqttQoS qosLevel; + private boolean isReatin; + private int expiry; + private String authToken; + + + public CoapRequestMessage(int version, CoapMessageType type, int tokenLength, CoapMessageCode code, int messageId, byte[] token, byte[] payload, InetSocketAddress remoteAddress) { + super(version, type, tokenLength, code, messageId, token, payload, remoteAddress); + } + + public CoapRequestMessage(int version, CoapMessageType type, int tokenLength, CoapMessageCode code, int messageId, byte[] token, InetSocketAddress remoteAddress) { + super(version, type, tokenLength, code, messageId, token, remoteAddress); + } + + public CoapRequestType getRequestType() { + return requestType; + } + + public void setRequestType(CoapRequestType requestType) { + this.requestType = requestType; + } + + public String getTopic() { + return topic; + } + + public void setTopic(String topic) { + this.topic = topic; + } + + public String getClientId() { + return clientId; + } + + public void setClientId(String clientId) { + this.clientId = clientId; + } + + public MqttQoS getQosLevel() { + return qosLevel; + } + + public void setQosLevel(MqttQoS qosLevel) { + this.qosLevel = qosLevel; + } + + public boolean isReatin() { + return isReatin; + } + + public void setReatin(boolean reatin) { + isReatin = reatin; + } + + public int getExpiry() { + return expiry; + } + + public void setExpiry(int expiry) { + this.expiry = expiry; + } + + public String getAuthToken() { + return authToken; + } + + public void setAuthToken(String authToken) { + this.authToken = authToken; + } +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java new file mode 100644 index 000000000..82e05faae --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; + +public enum CoapRequestType { + PUBLISH(0), + SUBSCRIBE(1), + + CONNECT(2), + HEARTBEAT(3), + DISCONNECT(4); + + private static final CoapRequestType[] VALUES; + private final int value; + + private CoapRequestType(int value) { + this.value = value; + } + + public int value() { + return this.value; + } + + public static CoapRequestType valueOf(int type) { + if (type >= 0 && type < VALUES.length) { + return VALUES[type]; + } else { + throw new IllegalArgumentException("Unknown CoapRequestType " + type); + } + } + + static { + CoapRequestType[] values = values(); + VALUES = new CoapRequestType[values.length + 1]; + + for (CoapRequestType type : values) { + int value = type.value; + if (VALUES[value] != null) { + throw new AssertionError("Value already in use: " + value + " by " + VALUES[value]); + } + VALUES[value] = type; + } + } + +} diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java index 948616e29..f59b9eb61 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java @@ -77,4 +77,12 @@ public class Constants { public static final int COAP_VERSION = 1; public static final int COAP_PAYLOAD_MARKER = 0xFF; public static final int COAP_MAX_TOKEN_LENGTH = 8; + public static final String COAP_QUERY_DELIMITER = "="; + public static final String COAP_PS_PREFIX = "ps"; + public static final String COAP_CONNECTION_PREFIX_1 = "mqtt"; + public static final String COAP_CONNECTION_PREFIX_2 = "connection"; + public static final String COAP_QUERY_CLIENT_ID = "clientid"; + public static final String COAP_QUERY_QOS = "qos"; + public static final String COAP_QUERY_RETAIN = "retain"; + public static final String COAP_QUERY_EXPIRY = "expiry"; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java new file mode 100644 index 000000000..c679248df --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -0,0 +1,358 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap; + +import com.sun.org.apache.bcel.internal.Const; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.MessageToMessageDecoder; +import io.netty.handler.codec.mqtt.MqttQoS; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.common.model.Constants; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + + +public class CoapDecoder extends MessageToMessageDecoder { + + private CoapMessageType coapType; + private int coapTokenLength; + private CoapMessageCode coapCode; + private int coapMessageId; + private byte[] coapToken; + private byte[] coapPayload; + InetSocketAddress remoteAddress; + private boolean isObserve; + + private String errorContent; + private CoapMessageCode errorCode; + + @Override + public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) { + + ByteBuf in = packet.content(); + remoteAddress = packet.sender(); + + // The length of Coap message is at least 4 bytes. + if (in.readableBytes() < 4) { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The length of header must be at least 4 bytes!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // Handle first byte, including version, type, and token length. + int firstByte = in.readUnsignedByte(); + int version = (firstByte >> 6) & 0x03; + if (version != Constants.COAP_VERSION) { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: Version must be 1!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + coapType = CoapMessageType.valueOf((firstByte >> 4) & 0x03); + coapTokenLength = firstByte & 0x0F; + if (coapTokenLength > Constants.COAP_MAX_TOKEN_LENGTH) { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The length of token is too long!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // Handle code + try { + coapCode = CoapMessageCode.valueOf(in.readUnsignedByte()); + } catch (IllegalArgumentException e) { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The code is not defined!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + if (!CoapMessageCode.isRequestCode(coapCode)) { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The code must be a request code!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // Handle messageID + coapMessageId = in.readUnsignedShort(); + + // Handle token + if (in.readableBytes() < coapTokenLength) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The length of remaining readable bytes is less than tokenLength!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + coapToken = new byte[coapTokenLength]; + in.readBytes(coapToken); + + CoapRequestMessage coapMessage = new CoapRequestMessage(version, coapType, coapTokenLength, coapCode, coapMessageId, coapToken, remoteAddress); + + // Handle options + int nextByte; + int optionNumber = 0; + List uriPaths = new ArrayList<>(); + while (in.readableBytes() > 0) { + + nextByte = in.readUnsignedByte(); + if (nextByte == Constants.COAP_PAYLOAD_MARKER) { + break; + } + + int optionDelta = nextByte >> 4; + int optionLength = nextByte & 0x0F; + + if (optionDelta == 13) { + optionDelta += in.readUnsignedByte(); + } else if (optionDelta == 14) { + optionDelta += 255 + in.readUnsignedShort(); + } else if (optionDelta == 15) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: OptionDelta can not be 15!"; + sendErrorResponse(ctx); + in.skipBytes(in.readableBytes()); + return; + } + + optionNumber += optionDelta; // current optionNumber = last optionNumber + optionDelta + + if (!CoapMessageOptionNumber.isValid(optionNumber)) { + // Return 4.02 Response + errorCode = CoapMessageCode.BAD_OPTION; + errorContent = "Format-Error: Option number is not defined!"; + sendErrorResponse(ctx); + in.skipBytes(in.readableBytes()); + return; + } + + if (optionLength == 13) { + optionLength += in.readUnsignedByte(); + } else if (optionLength == 14) { + optionLength += 255 + in.readUnsignedShort(); + } else if (optionLength == 15) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: OptionLength can not be 15!"; + sendErrorResponse(ctx); + in.skipBytes(in.readableBytes()); + return; + } + + if (in.readableBytes() < optionLength) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The number of readable bytes is less than optionLength"; + sendErrorResponse(ctx); + in.skipBytes(in.readableBytes()); + return; + } + byte[] optionValue = new byte[optionLength]; + in.readBytes(optionValue); + + if (optionNumber == CoapMessageOptionNumber.URI_PATH.value()) { + uriPaths.add(new String(optionValue, StandardCharsets.UTF_8)); + } + + if (optionNumber == CoapMessageOptionNumber.URI_QUERY.value()) { + String query = new String(optionValue, StandardCharsets.UTF_8); + String[] parts = query.split(Constants.COAP_QUERY_DELIMITER, 2); + if (parts.length != 2) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format of Observe is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + switch (parts[0]) { + case Constants.COAP_QUERY_CLIENT_ID: + coapMessage.setClientId(parts[1]); + break; + case Constants.COAP_QUERY_QOS: + coapMessage.setQosLevel(MqttQoS.valueOf(Integer.parseInt(parts[1]))); + break; + case Constants.COAP_QUERY_RETAIN: + coapMessage.setReatin(Boolean.parseBoolean(parts[1])); + break; + case Constants.COAP_QUERY_EXPIRY: + coapMessage.setExpiry(Integer.parseInt(parts[1])); + break; + default: + break; + } + } + + if (optionNumber == CoapMessageOptionNumber.OBSERVE.value()) { + if (optionValue.length == 1 && optionValue[0] == 0) { + isObserve = true; + } else { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format of Observe is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + } + + coapMessage.addOption(new CoapMessageOption(optionNumber, optionValue)); + } + + if (uriPaths.isEmpty()) { + // Return 4.00 Response + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // Handle Uri-Path + if (uriPaths.get(0).equals(Constants.COAP_PS_PREFIX)) { + switch (coapCode) { + case GET: + if (isObserve) { + coapMessage.setRequestType(CoapRequestType.SUBSCRIBE); + } else { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + break; + case POST: + coapMessage.setRequestType(CoapRequestType.PUBLISH); + break; + default: + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // construct topic + coapMessage.setTopic(uriPaths.stream().skip(1).collect(Collectors.joining(Constants.MQTT_TOPIC_DELIMITER, "", Constants.MQTT_TOPIC_DELIMITER))); + + } else if (uriPaths.size() == 2 && uriPaths.get(0).equals(Constants.COAP_CONNECTION_PREFIX_1) && uriPaths.get(1).equals(Constants.COAP_CONNECTION_PREFIX_2)) { + switch (coapCode) { + case POST: + coapMessage.setRequestType(CoapRequestType.CONNECT); + break; + case DELETE: + coapMessage.setRequestType(CoapRequestType.DISCONNECT); + break; + case PUT: + coapMessage.setRequestType(CoapRequestType.HEARTBEAT); + break; + default: + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + } else { + errorCode = CoapMessageCode.BAD_REQUEST; + errorContent = "Format-Error: The Format is not correct!"; + sendErrorResponse(ctx); + // Skip unread bytes + in.skipBytes(in.readableBytes()); + return; + } + + // Handle payload + if (in.readableBytes() > 0) { + coapPayload = new byte[in.readableBytes()]; + in.readBytes(coapPayload); + coapMessage.setPayload(coapPayload); + } + +// sendTestResponse(ctx); + out.add(coapMessage); + } + + public void sendErrorResponse(ChannelHandlerContext ctx) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapType == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapToken == null ? 0 : coapTokenLength, + errorCode, + coapMessageId, + coapToken, + errorContent.getBytes(StandardCharsets.UTF_8), + remoteAddress + ); + ctx.writeAndFlush(response); + } + + public void sendTestResponse(ChannelHandlerContext ctx) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapType == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapToken == null ? 0 : coapTokenLength, + CoapMessageCode.Valid, + coapMessageId, + coapToken, + "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), + remoteAddress + ); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + + } +} \ No newline at end of file From aeaaf928bfbf2e5462d3a21156ac0a90a7260057 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 16:06:53 +0800 Subject: [PATCH 04/67] Fix bug CoapDecoder --- .../common/model/CoapMessageOptionNumber.java | 9 +++++---- .../mqtt/common/model/CoapRequestMessage.java | 18 ++++++++++++++++++ .../rocketmq/mqtt/common/model/Constants.java | 2 ++ .../mqtt/cs/protocol/coap/CoapDecoder.java | 6 +++++- .../rocketmq/mqtt/cs/starter/MqttServer.java | 3 ++- 5 files changed, 32 insertions(+), 6 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java index b691b7399..7c4dd07d5 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageOptionNumber.java @@ -35,7 +35,8 @@ public enum CoapMessageOptionNumber { SIZE_2(28), PROXY_URI(35), PROXY_SCHEME(39), - SIZE_1(60); + SIZE_1(60), + REQUST_TAG(292); private static final CoapMessageOptionNumber[] VALUES; private final int value; @@ -49,11 +50,11 @@ public int value() { } public static boolean isValid(int number) { - return number > 0 && number < 192 && VALUES[number] != null; + return number > 0 && number < 293 && VALUES[number] != null; } public static CoapMessageOptionNumber valueOf(int number) { - if (number > 0 && number < 192 && VALUES[number] != null) { + if (number > 0 && number < 293 && VALUES[number] != null) { return VALUES[number]; } else { throw new IllegalArgumentException("Unknown CoapMessageOptionNumber " + number); @@ -62,7 +63,7 @@ public static CoapMessageOptionNumber valueOf(int number) { static { CoapMessageOptionNumber[] values = values(); - VALUES = new CoapMessageOptionNumber[192]; // Using 192 since the highest defined code is 192 + VALUES = new CoapMessageOptionNumber[293]; for (CoapMessageOptionNumber number : values) { int value = number.value; diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java index 5b5fc1824..4efc99892 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java @@ -13,6 +13,8 @@ public class CoapRequestMessage extends CoapMessage { private boolean isReatin; private int expiry; private String authToken; + private String userName; + private String password; public CoapRequestMessage(int version, CoapMessageType type, int tokenLength, CoapMessageCode code, int messageId, byte[] token, byte[] payload, InetSocketAddress remoteAddress) { @@ -78,4 +80,20 @@ public String getAuthToken() { public void setAuthToken(String authToken) { this.authToken = authToken; } + + public String getUserName() { + return userName; + } + + public void setUserName(String userName) { + this.userName = userName; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } } diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java index f59b9eb61..9b5d83037 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java @@ -85,4 +85,6 @@ public class Constants { public static final String COAP_QUERY_QOS = "qos"; public static final String COAP_QUERY_RETAIN = "retain"; public static final String COAP_QUERY_EXPIRY = "expiry"; + public static final String COAP_QUERY_USER_NAME = "username"; + public static final String COAP_QUERY_PASSWORD = "password"; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index c679248df..b5adbcaff 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -222,13 +222,17 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List Date: Tue, 23 Jul 2024 16:44:25 +0800 Subject: [PATCH 05/67] Add CoapEncoder --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 2 +- .../mqtt/cs/protocol/coap/CoapEncoder.java | 85 +++++++++++++++++++ .../rocketmq/mqtt/cs/starter/MqttServer.java | 3 +- 3 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index b5adbcaff..243afcb9a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -323,7 +323,7 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List { + + @Override + public void encode(ChannelHandlerContext ctx, CoapMessage msg, List out) throws Exception { + + ByteBuf buffer = Unpooled.buffer(); + + // Handle Version | Type | TokenLength + byte firstByte = (byte)((msg.getVersion() << 6) | (msg.getType().value() << 4) | (msg.getTokenLength() & 0x0F)); + buffer.writeByte(firstByte); + + // Handle Code, MessageID, Token + buffer.writeByte(msg.getCode().value()); + buffer.writeShort(msg.getMessageId()); + buffer.writeBytes(msg.getToken()); + + // Handle Options + if (!msg.getOptions().isEmpty()) { + int prevOptionNumber = 0; + for (CoapMessageOption option : msg.getOptions()) { + int optionDelta = option.getOptionNumber().value() - prevOptionNumber; + prevOptionNumber = option.getOptionNumber().value(); + int optionLength = option.getOptionValue().length; + + if (optionDelta < 13) { + buffer.writeByte((byte)((optionDelta << 4) | (optionLength & 0x0F))); + } else if (optionDelta < 269) { + buffer.writeByte((byte)((13 << 4) | (optionLength & 0x0F))); + buffer.writeByte(optionDelta - 13); + } else { + buffer.writeByte((byte)((14 << 4) | (optionLength & 0x0F))); + buffer.writeShort(optionDelta - 269); + } + + if (optionLength > 12 && optionLength < 269) { + buffer.writeByte(optionLength - 13); + } else if (optionLength >= 269) { + buffer.writeShort(optionLength - 269); + } + + buffer.writeBytes(option.getOptionValue()); + } + } + + // Handle Payload if not empty + if (msg.getPayload() != null && msg.getPayload().length > 0) { + buffer.writeByte((byte)0xFF); + buffer.writeBytes(msg.getPayload()); + } + + // Send Response + DatagramPacket responsePacket = new DatagramPacket(buffer, msg.getRemoteAddress()); + out.add(responsePacket); + + } + +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 3bd0fd1d8..960269a58 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -58,6 +58,7 @@ import org.apache.rocketmq.mqtt.cs.protocol.ws.WebSocketEncoder; import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapDecoder; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapEncoder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -311,7 +312,7 @@ private void startCoap() { protected void initChannel(DatagramChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // pipeline.addLast("coap-handler", new CoapHandler()); -// pipeline.addLast("coap-encoder", new CoapEncoder()); + pipeline.addLast("coap-encoder", new CoapEncoder()); pipeline.addLast("coap-decoder", new CoapDecoder()); // pipeline.addLast("coap-dispatcher", coapPacketDispatcher); } From ff050635fc4134182f121856312243f93b94a76c Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 17:30:22 +0800 Subject: [PATCH 06/67] Add coap relative handlers and processors --- .../mqtt/cs/protocol/CoapPacketHandler.java | 43 +++++ .../protocol/coap/CoapPacketDispatcher.java | 175 ++++++++++++++++++ .../coap/handler/CoapConnectHandler.java | 19 ++ .../coap/handler/CoapDisconnectHandler.java | 19 ++ .../coap/handler/CoapHeartbeatHandler.java | 19 ++ .../coap/handler/CoapPublishHandler.java | 19 ++ .../coap/handler/CoapSubscribeHandler.java | 19 ++ .../rocketmq/mqtt/cs/starter/MqttServer.java | 6 +- .../upstream/coap/CoapUpstreamProcessor.java | 32 ++++ .../coap/processor/CoapConnectProcessor.java | 14 ++ .../processor/CoapDisconnectProcessor.java | 14 ++ .../processor/CoapHeartbeatProcessor.java | 14 ++ .../coap/processor/CoapPublishProcessor.java | 14 ++ .../processor/CoapSubscribeProcessor.java | 14 ++ 14 files changed, 420 insertions(+), 1 deletion(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java new file mode 100644 index 000000000..42f19de85 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; + +public interface CoapPacketHandler { + + /** + * preHandler before upstream processor, for preprocessing + * + * @param ctx + * @param coapMessage + * @return + */ + boolean preHandler(ChannelHandlerContext ctx, T coapMessage); + + /** + * doHandler after upstream processor + * + * @param ctx + * @param coapMessage + * @param upstreamHookResult + */ + void doHandler(ChannelHandlerContext ctx, T coapMessage, HookResult upstreamHookResult); + +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java new file mode 100644 index 000000000..fa1a7c237 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java @@ -0,0 +1,175 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap; + +import io.netty.channel.ChannelException; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapPublishHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapSubscribeHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapConnectHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapHeartbeatHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapPublishProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapSubscribeProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapConnectProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapHeartbeatProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapDisconnectProcessor; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.util.concurrent.CompletableFuture; + +@Component +public class CoapPacketDispatcher extends SimpleChannelInboundHandler { + + private static Logger logger = LoggerFactory.getLogger(CoapPacketDispatcher.class); + @Resource + private CoapPublishHandler coapPublishHandler; + + @Resource + private CoapSubscribeHandler coapSubscribeHandler; + + @Resource + private CoapConnectHandler coapConnectHandler; + + @Resource + private CoapHeartbeatHandler coapHeartbeatHandler; + + @Resource + private CoapDisconnectHandler coapDisconnectHandler; + + @Resource + private CoapPublishProcessor coapPublishProcessor; + + @Resource + private CoapSubscribeProcessor coapSubscribeProcessor; + + @Resource + private CoapConnectProcessor coapConnectProcessor; + + @Resource + private CoapHeartbeatProcessor coapHeartbeatProcessor; + + @Resource + private CoapDisconnectProcessor coapDisconnectProcessor; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, CoapRequestMessage msg) throws Exception { + + boolean preResult = preHandler(ctx, msg); + if (!preResult) { + return; + } + CompletableFuture processResult; + try { + processResult = processCoapMessage(msg); + if (processResult == null) { + _channelRead0(ctx, msg, null); + return; + } + } catch (Throwable t) { + logger.error("", t); + throw new ChannelException(t.getMessage()); + } + processResult.whenComplete((hookResult, throwable) -> { + if (throwable != null) { + logger.error("", throwable); + ctx.fireExceptionCaught(new ChannelException(throwable.getMessage())); + return; + } + if (hookResult == null) { + ctx.fireExceptionCaught(new ChannelException("Coap UpstreamHook Result Unknown")); + return; + } + try { + _channelRead0(ctx, msg, hookResult); + } catch (Throwable t) { + logger.error("", t); + ctx.fireExceptionCaught(new ChannelException(t.getMessage())); + } + }); + } + + private void _channelRead0(ChannelHandlerContext ctx, CoapRequestMessage msg, HookResult processResult) { + switch (msg.getRequestType()) { + case PUBLISH: + coapPublishHandler.doHandler(ctx, msg, processResult); + break; + case SUBSCRIBE: + coapSubscribeHandler.doHandler(ctx, msg, processResult); + break; + case CONNECT: + coapConnectHandler.doHandler(ctx, msg, processResult); + break; + case HEARTBEAT: + coapHeartbeatHandler.doHandler(ctx, msg, processResult); + break; + case DISCONNECT: + coapDisconnectHandler.doHandler(ctx, msg, processResult); + break; + default: + } + } + + private boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage msg) { + switch (msg.getRequestType()) { + case PUBLISH: + return coapPublishHandler.preHandler(ctx, msg); + case SUBSCRIBE: + return coapSubscribeHandler.preHandler(ctx, msg); + case CONNECT: + return coapConnectHandler.preHandler(ctx, msg); + case HEARTBEAT: + return coapHeartbeatHandler.preHandler(ctx, msg); + case DISCONNECT: + return coapDisconnectHandler.preHandler(ctx, msg); + default: + return false; + } + } + + public CompletableFuture processCoapMessage(CoapRequestMessage msg) { + switch (msg.getRequestType()) { + case PUBLISH: + return coapPublishProcessor.process(msg); + break; + case SUBSCRIBE: + return coapSubscribeProcessor.process(msg); + break; + case CONNECT: + return coapConnectProcessor.process(msg); + break; + case HEARTBEAT: + return coapHeartbeatProcessor.process(msg); + break; + case DISCONNECT: + return coapDisconnectProcessor.process(msg); + break; + default: + } + CompletableFuture hookResult = new CompletableFuture<>(); + hookResult.complete(new HookResult(HookResult.FAIL, "InvalidCoapMsgCode", null)); + return hookResult; + } + +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java new file mode 100644 index 000000000..ca9791ee4 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -0,0 +1,19 @@ +package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; + +public class CoapConnectHandler implements CoapPacketHandler { + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return false; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java new file mode 100644 index 000000000..c4c66670e --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -0,0 +1,19 @@ +package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; + +public class CoapDisconnectHandler implements CoapPacketHandler { + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return false; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java new file mode 100644 index 000000000..f710a57f1 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -0,0 +1,19 @@ +package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; + +public class CoapHeartbeatHandler implements CoapPacketHandler { + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return false; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java new file mode 100644 index 000000000..527cdb56c --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -0,0 +1,19 @@ +package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; + +public class CoapPublishHandler implements CoapPacketHandler { + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return false; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java new file mode 100644 index 000000000..82281d69d --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -0,0 +1,19 @@ +package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; + +public class CoapSubscribeHandler implements CoapPacketHandler { + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return false; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 960269a58..8d33db048 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -59,6 +59,7 @@ import org.apache.rocketmq.remoting.common.TlsMode; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapDecoder; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapEncoder; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapPacketDispatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; @@ -91,6 +92,9 @@ public class MqttServer { @Resource private Mqtt5PacketDispatcher mqtt5PacketDispatcher; + @Resource + private CoapPacketDispatcher coapPacketDispatcher; + @Resource private WebSocketServerHandler webSocketServerHandler; @@ -314,7 +318,7 @@ protected void initChannel(DatagramChannel ch) throws Exception { // pipeline.addLast("coap-handler", new CoapHandler()); pipeline.addLast("coap-encoder", new CoapEncoder()); pipeline.addLast("coap-decoder", new CoapDecoder()); -// pipeline.addLast("coap-dispatcher", coapPacketDispatcher); + pipeline.addLast("coap-dispatcher", coapPacketDispatcher); } }); coapBootstrap.bind(); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java new file mode 100644 index 000000000..7e5072fd4 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java @@ -0,0 +1,32 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap;/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.remoting.exception.RemotingException; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +public interface CoapUpstreamProcessor { + /** + * process mqtt upstream packet + * @param msg + * @return + */ + CompletableFuture process(CoapRequestMessage msg) throws RemotingException, com.alipay.sofa.jraft.error.RemotingException, ExecutionException, InterruptedException; +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java new file mode 100644 index 000000000..6ccf9a0a9 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java @@ -0,0 +1,14 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; + +import java.util.concurrent.CompletableFuture; + +public class CoapConnectProcessor implements CoapUpstreamProcessor { + @Override + public CompletableFuture process(CoapRequestMessage msg) { + return null; + } +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java new file mode 100644 index 000000000..d67e71c68 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java @@ -0,0 +1,14 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; + +import java.util.concurrent.CompletableFuture; + +public class CoapDisconnectProcessor implements CoapUpstreamProcessor { + @Override + public CompletableFuture process(CoapRequestMessage msg) { + return null; + } +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java new file mode 100644 index 000000000..b320a6a86 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java @@ -0,0 +1,14 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; + +import java.util.concurrent.CompletableFuture; + +public class CoapHeartbeatProcessor implements CoapUpstreamProcessor { + @Override + public CompletableFuture process(CoapRequestMessage msg) { + return null; + } +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java new file mode 100644 index 000000000..daa39dcb1 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -0,0 +1,14 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; + +import java.util.concurrent.CompletableFuture; + +public class CoapPublishProcessor implements CoapUpstreamProcessor { + @Override + public CompletableFuture process(CoapRequestMessage msg) { + return null; + } +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java new file mode 100644 index 000000000..967551a72 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java @@ -0,0 +1,14 @@ +package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; + +import java.util.concurrent.CompletableFuture; + +public class CoapSubscribeProcessor implements CoapUpstreamProcessor { + @Override + public CompletableFuture process(CoapRequestMessage msg) { + return null; + } +} From addb17b75afcd382d52d5923c87ee600031b98e7 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 23 Jul 2024 17:49:32 +0800 Subject: [PATCH 07/67] Add todo mark of coap handlers and processors --- .../mqtt/cs/protocol/coap/handler/CoapConnectHandler.java | 4 +++- .../mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java | 4 +++- .../mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java | 4 +++- .../mqtt/cs/protocol/coap/handler/CoapPublishHandler.java | 3 ++- .../mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java | 3 ++- .../mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java | 1 + .../ds/upstream/coap/processor/CoapDisconnectProcessor.java | 1 + .../ds/upstream/coap/processor/CoapHeartbeatProcessor.java | 1 + .../mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java | 1 + .../ds/upstream/coap/processor/CoapSubscribeProcessor.java | 1 + 10 files changed, 18 insertions(+), 5 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index ca9791ee4..3d048b8ce 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -9,11 +9,13 @@ public class CoapConnectHandler implements CoapPacketHandler @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + // todo: check auth + // todo: check client id duplication return false; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - + // todo: response ack and return token } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index c4c66670e..ff8d1c81d 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -9,11 +9,13 @@ public class CoapDisconnectHandler implements CoapPacketHandler @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + // todo: check token if connection mode return false; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - + // todo: response ack } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 82281d69d..7d45112d3 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -9,11 +9,12 @@ public class CoapSubscribeHandler implements CoapPacketHandler process(CoapRequestMessage msg) { + // todo: RPC broadcast (store token) return null; } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java index d67e71c68..4635625ec 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java @@ -9,6 +9,7 @@ public class CoapDisconnectProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { + // RPC broadcast (clear token) return null; } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java index b320a6a86..6b986c0c7 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java @@ -9,6 +9,7 @@ public class CoapHeartbeatProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { + // todo: RPC broadcast (refresh token alive time) return null; } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index daa39dcb1..0391bafb7 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -9,6 +9,7 @@ public class CoapPublishProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { + // todo: store into RocketMQ return null; } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java index 967551a72..82d7075de 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java @@ -9,6 +9,7 @@ public class CoapSubscribeProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { + // todo: add subscription relationship return null; } } From 5f1d3eb50706f8958a51f8e586e55f610e1b6263 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 24 Jul 2024 11:27:07 +0800 Subject: [PATCH 08/67] fix bug --- .../rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java index fa1a7c237..c723bc5c0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java @@ -152,19 +152,14 @@ public CompletableFuture processCoapMessage(CoapRequestMessage msg) switch (msg.getRequestType()) { case PUBLISH: return coapPublishProcessor.process(msg); - break; case SUBSCRIBE: return coapSubscribeProcessor.process(msg); - break; case CONNECT: return coapConnectProcessor.process(msg); - break; case HEARTBEAT: return coapHeartbeatProcessor.process(msg); - break; case DISCONNECT: return coapDisconnectProcessor.process(msg); - break; default: } CompletableFuture hookResult = new CompletableFuture<>(); From 18a32de5bb46df1864ae6886b4990df3aae4e8f6 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 24 Jul 2024 11:31:03 +0800 Subject: [PATCH 09/67] add component annotation --- .../mqtt/cs/protocol/coap/handler/CoapConnectHandler.java | 2 ++ .../mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java | 2 ++ .../mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java | 2 ++ .../mqtt/cs/protocol/coap/handler/CoapPublishHandler.java | 2 ++ .../mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java | 2 ++ .../mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java | 2 ++ .../ds/upstream/coap/processor/CoapDisconnectProcessor.java | 2 ++ .../mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java | 2 ++ .../mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java | 2 ++ .../mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java | 2 ++ 10 files changed, 20 insertions(+) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index 3d048b8ce..c1fdf1da5 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -4,7 +4,9 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.springframework.stereotype.Component; +@Component public class CoapConnectHandler implements CoapPacketHandler { @Override diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index ff8d1c81d..06b2455ab 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -4,7 +4,9 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.springframework.stereotype.Component; +@Component public class CoapDisconnectHandler implements CoapPacketHandler { @Override diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java index 4e8fcb0d1..fa4b824b3 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -4,7 +4,9 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.springframework.stereotype.Component; +@Component public class CoapHeartbeatHandler implements CoapPacketHandler { @Override diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java index c576e3765..74db93839 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -4,7 +4,9 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.springframework.stereotype.Component; +@Component public class CoapPublishHandler implements CoapPacketHandler { @Override diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 7d45112d3..4f6bca829 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -4,7 +4,9 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.springframework.stereotype.Component; +@Component public class CoapSubscribeHandler implements CoapPacketHandler { @Override diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java index 87e6560a1..2d6b09c9c 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java @@ -3,9 +3,11 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; +@Component public class CoapConnectProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java index 4635625ec..7df83d876 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java @@ -3,9 +3,11 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; +@Component public class CoapDisconnectProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java index 6b986c0c7..96e6b28f8 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java @@ -3,9 +3,11 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; +@Component public class CoapHeartbeatProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index 0391bafb7..456497df3 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -3,9 +3,11 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; +@Component public class CoapPublishProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java index 82d7075de..992e8391c 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java @@ -3,9 +3,11 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; +@Component public class CoapSubscribeProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage msg) { From 2f238ae626b40394eceeabc5ce1d4fe789860ce3 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 24 Jul 2024 15:42:35 +0800 Subject: [PATCH 10/67] Complete coap message storage without coap notification, now a new message published by coap can be accepted by mqtt consumer --- .../mqtt/common/model/CoapRequestMessage.java | 18 +++- .../mqtt/common/util/MessageUtil.java | 17 ++++ .../mqtt/cs/protocol/coap/CoapDecoder.java | 4 +- .../coap/handler/CoapPublishHandler.java | 38 +++++++- .../coap/processor/CoapPublishProcessor.java | 91 ++++++++++++++++++- 5 files changed, 160 insertions(+), 8 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java index 4efc99892..21846a949 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java @@ -9,7 +9,7 @@ public class CoapRequestMessage extends CoapMessage { private CoapRequestType requestType; private String topic; private String clientId; - private MqttQoS qosLevel; + private MqttQoS qosLevel = MqttQoS.AT_MOST_ONCE; private boolean isReatin; private int expiry; private String authToken; @@ -25,6 +25,20 @@ public CoapRequestMessage(int version, CoapMessageType type, int tokenLength, Co super(version, type, tokenLength, code, messageId, token, remoteAddress); } + public CoapRequestMessage copy() { + CoapRequestMessage msg = new CoapRequestMessage(getVersion(), getType(), getTokenLength(), getCode(), getMessageId(), getToken(), getPayload(), getRemoteAddress()); + msg.setRequestType(requestType); + msg.setTopic(topic); + msg.setClientId(clientId); + msg.setQosLevel(qosLevel); + msg.setReatin(isReatin); + msg.setExpiry(expiry); + msg.setAuthToken(authToken); + msg.setUserName(userName); + msg.setPassword(password); + return msg; + } + public CoapRequestType getRequestType() { return requestType; } @@ -96,4 +110,6 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + + } diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java index 6b864686d..770532fa9 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java @@ -32,6 +32,7 @@ import io.netty.handler.codec.mqtt.MqttQoS; import io.netty.util.CharsetUtil; import org.apache.rocketmq.common.message.MessageDecoder; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.Message; import java.nio.ByteBuffer; @@ -137,6 +138,22 @@ public static Message toMessage(MqttPublishMessage mqttMessage) { return message; } + public static Message toMessage(CoapRequestMessage coapMessage) { + Message message = new Message(); + message.setFirstTopic(TopicUtils.decode(coapMessage.getTopic()).getFirstTopic()); + message.setOriginTopic(coapMessage.getTopic()); + message.setRetained(coapMessage.isReatin()); + message.putUserProperty(Message.extPropertyQoS, String.valueOf(coapMessage.getQosLevel().value())); + int readableBytes = coapMessage.getPayload().length; + byte[] body = new byte[readableBytes]; + System.arraycopy(coapMessage.getPayload(), 0, body, 0, readableBytes); + message.setPayload(body); + + // todo: add other properties, topicAlias, expiry, etc + + return message; + } + public static MqttPublishMessage removeRetainedFlag(MqttPublishMessage mqttPublishMessage) { MqttFixedHeader tmpFixHeader = mqttPublishMessage.fixedHeader(); mqttPublishMessage = new MqttPublishMessage(new MqttFixedHeader(tmpFixHeader.messageType(), tmpFixHeader.isDup(), tmpFixHeader.qosLevel(), false, tmpFixHeader.remainingLength()), diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 243afcb9a..3f78871bb 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -286,7 +286,7 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { // todo: check token if connection mode - return false; + return true; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - // todo: response ack + if (upstreamHookResult.isSuccess()) { + doResponseSuccess(ctx, coapMessage); + } else { + doResponseFail(ctx, coapMessage, upstreamHookResult.getRemark()); + } + } + + public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, String errContent) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId(), + coapMessage.getToken(), + errContent.getBytes(), + coapMessage.getRemoteAddress() + ); + ctx.writeAndFlush(response); + } + + public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.CREATED, + coapMessage.getMessageId(), + coapMessage.getToken(), + null, + coapMessage.getRemoteAddress() + ); + ctx.writeAndFlush(response); } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index 456497df3..5bad78faa 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -1,17 +1,104 @@ package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; +import com.alibaba.fastjson.JSON; +import org.apache.rocketmq.common.message.MessageClientIDSetter; +import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; +import org.apache.rocketmq.mqtt.common.facade.RetainedPersistManager; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.Message; +import org.apache.rocketmq.mqtt.common.model.MqttTopic; +import org.apache.rocketmq.mqtt.common.model.StoreResult; +import org.apache.rocketmq.mqtt.common.util.MessageUtil; +import org.apache.rocketmq.mqtt.common.util.TopicUtils; +import org.apache.rocketmq.mqtt.ds.meta.FirstTopicManager; +import org.apache.rocketmq.mqtt.ds.meta.WildcardManager; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.mqtt.processor.PublishProcessor; +import org.apache.rocketmq.mqtt.exporter.collector.MqttMetricsCollector; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.util.Set; import java.util.concurrent.CompletableFuture; @Component public class CoapPublishProcessor implements CoapUpstreamProcessor { + private static Logger logger = LoggerFactory.getLogger(PublishProcessor.class); + + @Resource + private LmqQueueStore lmqQueueStore; + + @Resource + private WildcardManager wildcardManager; + + @Resource + private FirstTopicManager firstTopicManager; + + @Resource + RetainedPersistManager retainedPersistManager; + @Override public CompletableFuture process(CoapRequestMessage msg) { - // todo: store into RocketMQ - return null; + CompletableFuture r = put(msg); + return r.thenCompose(storeResult -> HookResult.newHookResult(HookResult.SUCCESS, null, + JSON.toJSONBytes(storeResult))); + } + + public CompletableFuture put(CoapRequestMessage coapMessage) { + boolean isEmpty = false; + // deal empty payload + if (coapMessage.getPayload() == null || coapMessage.getPayload().length == 0) { + coapMessage.setPayload(MessageUtil.EMPTYSTRING.getBytes(StandardCharsets.UTF_8)); + isEmpty = true; + } + + String originTopic = coapMessage.getTopic(); + String pubTopic = TopicUtils.normalizeTopic(originTopic); + MqttTopic mqttTopic = TopicUtils.decode(pubTopic); + firstTopicManager.checkFirstTopicIfCreated(mqttTopic.getFirstTopic()); // Check the firstTopic is existed + Set queueNames = wildcardManager.matchQueueSetByMsgTopic(pubTopic, null); // Find queues by topic + + String msgId = MessageClientIDSetter.createUniqID(); + long bornTime = System.currentTimeMillis(); + + if (coapMessage.isReatin()) { + CoapRequestMessage retainedCoapMessage = coapMessage.copy(); + //Change the retained flag of message that will send MQ is 0 + retainedCoapMessage.setReatin(false); + // store retained message + Message metaMessage = MessageUtil.toMessage(retainedCoapMessage); + metaMessage.setMsgId(msgId); + metaMessage.setBornTimestamp(bornTime); + metaMessage.setEmpty(isEmpty); + CompletableFuture storeRetainedFuture = retainedPersistManager.storeRetainedMessage(TopicUtils.normalizeTopic(metaMessage.getOriginTopic()), metaMessage); + storeRetainedFuture.whenComplete((res, throwable) -> { + if (throwable != null) { + logger.error("Store topic:{} retained message error.{}", metaMessage.getOriginTopic(), throwable); + } + }); + } + + Message message = MessageUtil.toMessage(coapMessage); + message.setMsgId(msgId); + message.setBornTimestamp(bornTime); + message.setEmpty(isEmpty); + + collectWriteBytesAndTps(message.getFirstTopic(), message.getPayload().length); + + return lmqQueueStore.putMessage(queueNames, message); + + } + + private void collectWriteBytesAndTps(String topic, int length) { + try { + MqttMetricsCollector.collectReadWriteMatchActionBytes(length, topic, "put"); + MqttMetricsCollector.collectPutRequestTps(1, topic); + } catch (Throwable e) { + logger.error("Collect prometheus error", e); + } } } From 47fa71b68391e245c7ee3f2a5ef51c0ecb2dee5d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 29 Jul 2024 17:41:56 +0800 Subject: [PATCH 11/67] CoapSession relative, not completed --- .../mqtt/cs/protocol/coap/CoapEncoder.java | 6 +- .../coap/handler/CoapSubscribeHandler.java | 229 ++++++++++++++- .../rocketmq/mqtt/cs/session/CoapSession.java | 206 +++++++++++++ .../mqtt/cs/session/loop/CoapSessionLoop.java | 33 +++ .../cs/session/loop/CoapSessionLoopImpl.java | 271 ++++++++++++++++++ .../processor/CoapSubscribeProcessor.java | 34 ++- 6 files changed, 772 insertions(+), 7 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java index e8e6e171c..eefe27458 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java @@ -48,7 +48,7 @@ public void encode(ChannelHandlerContext ctx, CoapMessage msg, List out) for (CoapMessageOption option : msg.getOptions()) { int optionDelta = option.getOptionNumber().value() - prevOptionNumber; prevOptionNumber = option.getOptionNumber().value(); - int optionLength = option.getOptionValue().length; + int optionLength = option.getOptionValue() == null ? 0 : option.getOptionValue().length; if (optionDelta < 13) { buffer.writeByte((byte)((optionDelta << 4) | (optionLength & 0x0F))); @@ -66,7 +66,9 @@ public void encode(ChannelHandlerContext ctx, CoapMessage msg, List out) buffer.writeShort(optionLength - 269); } - buffer.writeBytes(option.getOptionValue()); + if (optionLength > 0) { + buffer.writeBytes(option.getOptionValue()); + } } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 4f6bca829..b9ad1cc31 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -1,22 +1,247 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.apache.rocketmq.mqtt.cs.protocol.mqtt.handler.MqttSubscribeHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + @Component public class CoapSubscribeHandler implements CoapPacketHandler { + private static Logger logger = LoggerFactory.getLogger(CoapSubscribeHandler.class); + + @Resource + private CoapSessionLoop sessionLoop; + + private ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("check_coap_subscribe_future")); + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { // todo: check token if connection mode - return false; +// byte[] byteArray = new byte[3]; +// intToByteArray(1, byteArray); +// doResponseSuccess(ctx, coapMessage, byteArray); +// +// coapMessage.setMessageId(coapMessage.getMessageId() + 1); +// intToByteArray(2, byteArray); +// doResponseCON(ctx, coapMessage, byteArray); +// +// coapMessage.setMessageId(coapMessage.getMessageId() + 1); +// intToByteArray(3, byteArray); +// doResponseCON(ctx, coapMessage, byteArray); +// +// coapMessage.setMessageId(coapMessage.getMessageId() + 1); +// intToByteArray(4, byteArray); +// doResponseCON(ctx, coapMessage, byteArray); +// +// coapMessage.setMessageId(coapMessage.getMessageId() + 1); +// intToByteArray(5, byteArray); +// doResponseCONError(ctx, coapMessage, byteArray); +// +// coapMessage.setMessageId(coapMessage.getMessageId() + 1); +// intToByteArray(6, byteArray); +// doResponseCON(ctx, coapMessage, byteArray); + return true; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { // todo: response ack + if (!upstreamHookResult.isSuccess()) { + doResponseFail(ctx, coapMessage, upstreamHookResult.getRemark()); + return; + } + + CompletableFuture future = new CompletableFuture<>(); + // todo: setFuture + scheduler.schedule(() -> { + if (!future.isDone()) { + future.complete(null); + } + }, 1, TimeUnit.SECONDS); + try { + Subscription subscription = new Subscription(); + subscription.setQos(coapMessage.getQosLevel().value()); + subscription.setTopicFilter(TopicUtils.normalizeTopic(coapMessage.getTopic())); + + CoapSession session = new CoapSession(); + session.setAddress(coapMessage.getRemoteAddress()); + session.setMessageId(coapMessage.getMessageId()); + session.setToken(coapMessage.getToken()); + session.setSubscribeTime(System.currentTimeMillis()); + session.setSubscription(subscription); + boolean addResult = sessionLoop.addSession(session); // if the session is already exist, do not send retained message + + future.thenAccept(aVoid -> { + if (!ctx.channel().isActive()) { + return; + } + // todo: removeFuture + doResponseSuccess(ctx, coapMessage); + // todo: sendRetainMessage() + + + }); + } catch (Exception e) { + logger.error("Coap Subscribe:{}", coapMessage.getRemoteAddress(), e); + } + + } + + public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, String errContent) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId() + 1, + coapMessage.getToken(), + errContent.getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(1))); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + } + + + // change an integer into a byte array with length 3 + private byte[] intToByteArray(int value) { + byte[] byteArray = new byte[3]; + byteArray[0] = (byte) (value >> 16); + byteArray[1] = (byte) (value >> 8); + byteArray[2] = (byte) (value); + return byteArray; + } + + public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.CONTENT, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(1))); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + + } + + public void doResponseNON(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, byte[] byteArray) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.CONTENT, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Hello, 111".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, byteArray)); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + } + + public void doResponseCON(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, byte[] byteArray) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + coapMessage.getTokenLength(), + CoapMessageCode.CONTENT, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Hello, 222".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, byteArray)); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + } + + public void doResponseCONError(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, byte[] byteArray) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + coapMessage.getTokenLength(), + CoapMessageCode.BAD_REQUEST, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Hello, 222".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, byteArray)); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } + } + + + public void doResponseRST(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, byte[] byteArray) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.RST, + coapMessage.getTokenLength(), + CoapMessageCode.BAD_REQUEST, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Hello, RST".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, byteArray)); + if (ctx.channel().isActive()) { + ctx.writeAndFlush(response); + } else { + System.out.println("Channel is not active"); + } } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java new file mode 100644 index 000000000..9ba483d93 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -0,0 +1,206 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session; + +import org.apache.rocketmq.mqtt.common.model.Message; +import org.apache.rocketmq.mqtt.common.model.Queue; +import org.apache.rocketmq.mqtt.common.model.QueueOffset; +import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.net.InetSocketAddress; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +public class CoapSession { + private static Logger logger = LoggerFactory.getLogger(CoapSession.class); + private InetSocketAddress address; + private int messageId; + private byte[] token; + private int messageNum = 0; + private long subscribeTime; + private volatile int pullSize; + + private Subscription subscription; + private Map offsetMap = new ConcurrentHashMap<>(16); + Map> sendingMessages = new ConcurrentHashMap<>(16); + + public CoapSession() {} + + public void refreshSubscribeTime() { + this.subscribeTime = System.currentTimeMillis(); + } + + public QueueOffset getQueueOffset(Queue queue) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + return offsetMap.get(queue); + } + + public void freshQueue(Set queues) { + if (this.subscription == null) { + throw new RuntimeException("subscription is null"); + } + if (queues == null) { + logger.warn("queues is null when freshQueue,{},{}", this.address, this.subscription); + return; + } + + for (Queue memQueue: offsetMap.keySet()) { + if (!queues.contains(memQueue)) { + offsetMap.remove(memQueue); + } + } + + // init queueOffset + for (Queue nowQueue : queues) { + if (!offsetMap.containsKey(nowQueue)) { + QueueOffset queueOffset = new QueueOffset(); + offsetMap.put(nowQueue, queueOffset); + // todo: this.markPersistOffsetFlag(true); + } + } + + for (Queue memQueue : sendingMessages.keySet()) { + if (!queues.contains(memQueue)) { + sendingMessages.remove(memQueue); + } + } + + if (queues.isEmpty()) { + logger.warn("queues is empty when freshQueue,{},{}", this.address, this.subscription); + } + } + + public void addOffset(Queue queue, QueueOffset offset) { + offsetMap.put(queue, offset); + } + + public void updateQueueOffset(Queue queue, Message message) { + if (!offsetMap.containsKey(queue)) { + logger.warn("failed update queue offset,not found queueOffset,{},{},{}", this.address, this.subscription, + queue); + return; + } + QueueOffset queueOffset = offsetMap.get(queue); + queueOffset.setOffset(message.getOffset() + 1); + } + + public boolean addSendingMessages(Queue queue, List messages) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + if (messages == null || messages.isEmpty()) { + return false; + } + if (subscription.isShare()) { + return true; + } + if (!sendingMessages.containsKey(queue)) { + sendingMessages.putIfAbsent(queue, new LinkedHashSet<>(8)); + } + if (!offsetMap.containsKey(queue)) { + logger.warn("not found queueOffset,{},{},{}", this.address, this.subscription, queue); + return false; + } + boolean add =false; + QueueOffset queueOffset = offsetMap.get(queue); + for (Message message : messages) { + if (message.getOffset() < queueOffset.getOffset() && queueOffset.getOffset() != Long.MAX_VALUE) { + continue; + } + synchronized (this) { + if (sendingMessages.get(queue).add(message.copy())) { + add = true; + } + } + } + return add; + } + + public boolean sendingMessageIsEmpty(Queue queue) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return true; + } + synchronized (this) { + return messages.isEmpty(); + } + } + + public InetSocketAddress getAddress() { + return address; + } + + public void setAddress(InetSocketAddress address) { + this.address = address; + } + + public int getMessageId() { + return messageId; + } + + public void setMessageId(int messageId) { + this.messageId = messageId; + } + + public byte[] getToken() { + return token; + } + + public void setToken(byte[] token) { + this.token = token; + } + + public int getMessageNum() { + return messageNum; + } + + public void setMessageNum(int messageNum) { + this.messageNum = messageNum; + } + + public long getSubscribeTime() { + return subscribeTime; + } + + public void setSubscribeTime(long subscribeTime) { + this.subscribeTime = subscribeTime; + } + + public Subscription getSubscription() { + return subscription; + } + + public void setSubscription(Subscription subscription) { + this.subscription = subscription; + } + + public int getPullSize() { + return pullSize; + } + + public void setPullSize(int pullSize) { + this.pullSize = pullSize; + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java new file mode 100644 index 000000000..20cf08222 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java @@ -0,0 +1,33 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session.loop; + +import org.apache.rocketmq.mqtt.cs.session.CoapSession; + +import java.net.InetSocketAddress; + +public interface CoapSessionLoop { + + boolean addSession(CoapSession session); + + CoapSession getSession(InetSocketAddress address); + + void removeSession(InetSocketAddress address); + + void notifyPullMessage(); +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java new file mode 100644 index 000000000..8fff28a1f --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -0,0 +1,271 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session.loop; + +import com.alibaba.fastjson.JSONObject; +import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; +import org.apache.rocketmq.mqtt.common.model.PullResult; +import org.apache.rocketmq.mqtt.common.model.Queue; +import org.apache.rocketmq.mqtt.common.model.QueueOffset; +import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.apache.rocketmq.mqtt.cs.config.ConnectConf; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +@Component +public class CoapSessionLoopImpl implements CoapSessionLoop{ + private static Logger logger = LoggerFactory.getLogger(CoapSessionLoopImpl.class); + + @Resource + private ConnectConf connectConf; + + @Resource + private LmqQueueStore lmqQueueStore; + + private ScheduledThreadPoolExecutor scheduler; + + private Map sessionMap = new ConcurrentHashMap<>(1024); + private Map pullEventMap = new ConcurrentHashMap<>(1024); + private Map pullStatus = new ConcurrentHashMap<>(1024); + + private AtomicLong rid = new AtomicLong(); + private long pullIntervalMillis = 10; + + @PostConstruct + public void init() { + scheduler = new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("coap_loop_scheduler_")); + } + + private void pullLoop() { + try { + for (Map.Entry entry : pullEventMap.entrySet()) { + PullEvent pullEvent = entry.getValue(); + CoapSession session = pullEvent.session; + if (Boolean.TRUE.equals(pullStatus.get(eventQueueKey(session, pullEvent.queue)))) { + continue; + } + doPull(pullEvent); + } + } catch (Exception e) { + logger.error("", e); + } + } + + + @Override + public boolean addSession(CoapSession session) { + // todo: addSubscriptionAndInit + InetSocketAddress address = session.getAddress(); + synchronized (this) { + // if this session is already exist, refresh the subscription time and do nothing + if (sessionMap.containsKey(address)) { + sessionMap.get(address).refreshSubscribeTime(); + return false; + } + sessionMap.put(address, session); + } + return true; + } + + @Override + public CoapSession getSession(InetSocketAddress address) { + return sessionMap.get(address); + } + + @Override + public void removeSession(InetSocketAddress address) { + + } + + @Override + public void notifyPullMessage() { + + } + + private void pullMessage(CoapSession session, Queue queue) { + if (session == null || queue == null) { + return; + } +// if (needLoadPersistedOffset(session, queue)) { +// return; +// } + if (!session.sendingMessageIsEmpty(queue)) { + scheduler.schedule(() -> pullMessage(session, queue), pullIntervalMillis, TimeUnit.MILLISECONDS); + } else { + PullEvent pullEvent = new PullEvent(session, queue); + pullEventMap.put(eventQueueKey(session, queue), pullEvent); + } + } + + private void doPull(PullEvent pullEvent) { + CoapSession session = pullEvent.session; + Subscription subscription = session.getSubscription(); + Queue queue = pullEvent.queue; + QueueOffset queueOffset = session.getQueueOffset(queue); + if (session.getSubscription() == null || queueOffset == null) { + clearPullStatus(session, queue, pullEvent); + return; + } + + if (!queueOffset.isInitialized()) { + initOffset(session, queue, queueOffset, null, null); + scheduler.schedule(() -> pullMessage(session, queue), pullIntervalMillis, TimeUnit.MILLISECONDS); + return; + } + + pullStatus.put(eventQueueKey(session, queue), true); + int count = session.getPullSize() > 0 ? session.getPullSize() : connectConf.getPullBatchSize(); + CompletableFuture result = new CompletableFuture<>(); + result.whenComplete((pullResult, throwable) -> { + if (throwable != null) { + clearPullStatus(session, queue, pullEvent); + logger.error("{}", session.getAddress(), throwable); + scheduler.schedule(() -> pullMessage(session, queue), 1, TimeUnit.SECONDS); + return; + } + try { + if (PullResult.PULL_SUCCESS == pullResult.getCode()) { + if (pullResult.getMessageList() != null && + pullResult.getMessageList().size() >= Math.min(count, connectConf.getMaxTransferCountOnMessageInDisk())) { + scheduler.schedule(() -> pullMessage(); + } + boolean add = session.addSendingMessages(queue, pullResult.getMessageList()); +// if (add) { +// pushAction.messageArrive(session, subscription, queue); +// } + } else if (PullResult.PULL_OFFSET_MOVED == pullResult.getCode()) { + queueOffset.setOffset(pullResult.getNextQueueOffset().getOffset()); +// session.markPersistOffsetFlag(true); + pullMessage(session, queue); + } else { + logger.error("response:{},{}", session.getAddress(), JSONObject.toJSONString(pullResult)); + } + } finally { + clearPullStatus(session, queue, pullEvent); + } + }); + } + + private void clearPullStatus(CoapSession session, Queue queue, PullEvent pullEvent) { + pullEventMap.remove(eventQueueKey(session, queue), pullEvent); + pullStatus.remove(eventQueueKey(session, queue)); + } + + private void futureDone(CompletableFuture future, AtomicInteger result) { + if (future == null) { + return; + } + if (result == null) { + return; + } + if (result.decrementAndGet() <= 0) { + future.complete(null); + } + } + + private void initOffset(CoapSession session, Queue queue, QueueOffset queueOffset, CompletableFuture future, AtomicInteger result) { + if (queueOffset.isInitialized()) { + futureDone(future, result); + return; + } + if (queueOffset.isInitializing()) { + return; + } + queueOffset.setInitializing(); + CompletableFuture queryResult = lmqQueueStore.queryQueueMaxOffset(queue); + queryResult.whenComplete((maxOffset, throwable) -> { + if (throwable != null) { + logger.error("queryQueueMaxId onException {}", queue.getQueueName(), throwable); + QueueOffset thisQueueOffset = session.getQueueOffset(queue); + if (thisQueueOffset != null) { + if (!thisQueueOffset.isInitialized()) { + thisQueueOffset.setOffset(Long.MAX_VALUE); + } + thisQueueOffset.setInitialized(); + } + futureDone(future, result); + return; + } + QueueOffset thisQueueOffset = session.getQueueOffset(queue); + if (thisQueueOffset != null) { + if (!thisQueueOffset.isInitialized()) { + thisQueueOffset.setOffset(maxOffset); + } + thisQueueOffset.setInitialized(); + } + futureDone(future, result); + }); + } + + private String eventQueueKey(CoapSession session, Queue queue) { + StringBuilder sb = new StringBuilder(); + sb.append(session.getAddress()); + sb.append("-"); + sb.append(queue.getQueueId()); + sb.append("-"); + sb.append(queue.getQueueName()); + sb.append("-"); + sb.append(queue.getBrokerName()); + return sb.toString(); + } + + class PullEvent { + private CoapSession session; + private Queue queue; + private long id = rid.getAndIncrement(); + + public PullEvent(CoapSession session, Queue queue) { + this.session = session; + this.queue = queue; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + PullEvent pullEvent = (PullEvent) o; + + return id == pullEvent.id; + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + } +} diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java index 992e8391c..4c8d2c4c6 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java @@ -1,17 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.MqttTopic; +import org.apache.rocketmq.mqtt.common.util.TopicUtils; +import org.apache.rocketmq.mqtt.ds.meta.FirstTopicManager; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; import org.springframework.stereotype.Component; +import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; @Component public class CoapSubscribeProcessor implements CoapUpstreamProcessor { + + @Resource + private FirstTopicManager firstTopicManager; + @Override - public CompletableFuture process(CoapRequestMessage msg) { - // todo: add subscription relationship - return null; + public CompletableFuture process(CoapRequestMessage coapMessage) { + String pubTopic = TopicUtils.normalizeTopic(coapMessage.getTopic()); + assert pubTopic != null; + MqttTopic mqttTopic = TopicUtils.decode(pubTopic); + firstTopicManager.checkFirstTopicIfCreated(mqttTopic.getFirstTopic()); + return HookResult.newHookResult(HookResult.SUCCESS, null, null); } } From 150f12d59dee40523757aa7ab3283935e13bb442 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 29 Jul 2024 20:20:16 +0800 Subject: [PATCH 12/67] CoapSession relative, not completed --- .../coap/handler/CoapSubscribeHandler.java | 2 +- .../rocketmq/mqtt/cs/session/CoapSession.java | 13 +++++- .../rocketmq/mqtt/cs/session/QueueFresh.java | 17 +++++++ .../mqtt/cs/session/loop/CoapSessionLoop.java | 5 ++- .../cs/session/loop/CoapSessionLoopImpl.java | 45 ++++++++++++++++--- .../mqtt/cs/session/match/MatchAction.java | 28 ++++++++++++ 6 files changed, 100 insertions(+), 10 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index b9ad1cc31..4b0d59b1e 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -101,7 +101,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, session.setToken(coapMessage.getToken()); session.setSubscribeTime(System.currentTimeMillis()); session.setSubscription(subscription); - boolean addResult = sessionLoop.addSession(session); // if the session is already exist, do not send retained message + sessionLoop.addSession(session, future); // if the session is already exist, do not send retained message future.thenAccept(aVoid -> { if (!ctx.channel().isActive()) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 9ba483d93..485f3b100 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -25,7 +25,10 @@ import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; -import java.util.*; +import java.util.Map; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.List; import java.util.concurrent.ConcurrentHashMap; public class CoapSession { @@ -203,4 +206,12 @@ public int getPullSize() { public void setPullSize(int pullSize) { this.pullSize = pullSize; } + + public Map getOffsetMap() { + return offsetMap; + } + + public void setOffsetMap(Map offsetMap) { + this.offsetMap = offsetMap; + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/QueueFresh.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/QueueFresh.java index 2b360d735..2cbb5b2bd 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/QueueFresh.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/QueueFresh.java @@ -60,4 +60,21 @@ public Set freshQueue(Session session, Subscription subscription) { return queues; } + public Set freshQueue(CoapSession session) { + Set queues = new HashSet<>(); + Subscription subscription = session.getSubscription(); + Set brokers = lmqQueueStore.getReadableBrokers(subscription.toFirstTopic()); + if (brokers == null || brokers.isEmpty()) { + return queues; + } + for (String broker : brokers) { + Queue moreQueue = new Queue(); + moreQueue.setQueueName(subscription.toQueueName()); + moreQueue.setBrokerName(broker); + queues.add(moreQueue); + } + session.freshQueue(queues); + return queues; + } + } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java index 20cf08222..0b1b4069a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java @@ -20,14 +20,15 @@ import org.apache.rocketmq.mqtt.cs.session.CoapSession; import java.net.InetSocketAddress; +import java.util.concurrent.CompletableFuture; public interface CoapSessionLoop { - boolean addSession(CoapSession session); + void addSession(CoapSession session, CompletableFuture future); CoapSession getSession(InetSocketAddress address); - void removeSession(InetSocketAddress address); + CoapSession removeSession(InetSocketAddress address); void notifyPullMessage(); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 8fff28a1f..062292fbb 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -26,6 +26,9 @@ import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.apache.rocketmq.mqtt.cs.session.QueueFresh; +import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.match.MatchAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -46,12 +49,20 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ private static Logger logger = LoggerFactory.getLogger(CoapSessionLoopImpl.class); + @Resource + private MatchAction matchAction; + @Resource private ConnectConf connectConf; @Resource private LmqQueueStore lmqQueueStore; + @Resource + private QueueFresh queueFresh; + + + private ScheduledThreadPoolExecutor pullService; private ScheduledThreadPoolExecutor scheduler; private Map sessionMap = new ConcurrentHashMap<>(1024); @@ -64,6 +75,7 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ @PostConstruct public void init() { scheduler = new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("coap_loop_scheduler_")); + pullService.scheduleWithFixedDelay(() -> pullLoop(), pullIntervalMillis, pullIntervalMillis, TimeUnit.MILLISECONDS); } private void pullLoop() { @@ -83,18 +95,26 @@ private void pullLoop() { @Override - public boolean addSession(CoapSession session) { + public void addSession(CoapSession session, CompletableFuture future) { // todo: addSubscriptionAndInit InetSocketAddress address = session.getAddress(); synchronized (this) { // if this session is already exist, refresh the subscription time and do nothing if (sessionMap.containsKey(address)) { sessionMap.get(address).refreshSubscribeTime(); - return false; + return; } sessionMap.put(address, session); } - return true; + // Init + AtomicInteger result = new AtomicInteger(0); + queueFresh.freshQueue(session); + Map offsetMap = session.getOffsetMap(); + result.addAndGet(offsetMap.size()); + for (Map.Entry entry : offsetMap.entrySet()) { + initOffset(session, entry.getKey(), entry.getValue(), future, result); + } + matchAction.addSubscription(session); } @Override @@ -103,8 +123,21 @@ public CoapSession getSession(InetSocketAddress address) { } @Override - public void removeSession(InetSocketAddress address) { - + public CoapSession removeSession(InetSocketAddress address) { + CoapSession session = null; + try { + synchronized (this) { + session = sessionMap.remove(address); + } + // todo: inFlyCache.cleanResource() + if (session != null) { + matchAction.removeSubscription(session); + // todo: persistOffset(session) + } + } catch (Exception e) { + logger.error("unloadSession fail:{}", address, e); + } + return session; } @Override @@ -157,7 +190,7 @@ private void doPull(PullEvent pullEvent) { if (PullResult.PULL_SUCCESS == pullResult.getCode()) { if (pullResult.getMessageList() != null && pullResult.getMessageList().size() >= Math.min(count, connectConf.getMaxTransferCountOnMessageInDisk())) { - scheduler.schedule(() -> pullMessage(); + scheduler.schedule(() -> pullMessage(session, queue), pullIntervalMillis, TimeUnit.MILLISECONDS); } boolean add = session.addSendingMessages(queue, pullResult.getMessageList()); // if (add) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java index 84bc4755c..a71207e0b 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java @@ -23,6 +23,7 @@ import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.model.Trie; import org.apache.rocketmq.mqtt.common.util.TopicUtils; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.Session; import org.apache.rocketmq.mqtt.cs.session.loop.SessionLoop; import org.slf4j.Logger; @@ -30,6 +31,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.net.InetSocketAddress; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -48,6 +50,8 @@ public class MatchAction { private Trie trie = new Trie<>(); private ConcurrentMap> topicCache = new ConcurrentHashMap<>(16); + private ConcurrentMap> coapTopicCache = new ConcurrentHashMap<>(16); + public Set> matchClients(String topic, String namespace) { Set> result = new HashSet<>(); @@ -126,6 +130,16 @@ public void addSubscription(Session session, Set subscriptions) { } } + public void addSubscription(CoapSession session) { + Subscription subscription = session.getSubscription(); + String topicFilter = subscription.getTopicFilter(); + + synchronized (coapTopicCache) { + coapTopicCache.putIfAbsent(topicFilter, new HashSet<>()); + coapTopicCache.get(topicFilter).add(session.getAddress()); + } + } + public void removeSubscription(Session session, Set subscriptions) { String channelId = session.getChannelId(); if (channelId == null || subscriptions == null || subscriptions.isEmpty()) { @@ -158,4 +172,18 @@ public void removeSubscription(String channelId, Subscription subscription) { } } + public void removeSubscription(CoapSession session) { + String topicFilter = session.getSubscription().getTopicFilter(); + + synchronized (coapTopicCache) { + Set addressSet = coapTopicCache.get(topicFilter); + if (addressSet != null) { + addressSet.remove(session.getAddress()); + if (addressSet.isEmpty()) { + coapTopicCache.remove(topicFilter); + } + } + } + } + } From 3a06830a571396bcd54648f216d3856256c225a1 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 29 Jul 2024 20:23:02 +0800 Subject: [PATCH 13/67] CoapSession relative, not completed --- .../rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java | 1 + 1 file changed, 1 insertion(+) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 062292fbb..e11e4ceb3 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -74,6 +74,7 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ @PostConstruct public void init() { + pullService = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_pull_message_thread_")); scheduler = new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("coap_loop_scheduler_")); pullService.scheduleWithFixedDelay(() -> pullLoop(), pullIntervalMillis, pullIntervalMillis, TimeUnit.MILLISECONDS); } From fb2b860c22d26be9ba6d49ee557e5b3969304009 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 30 Jul 2024 15:38:30 +0800 Subject: [PATCH 14/67] CoapSession relative, not completed --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 2 + .../coap/handler/CoapSubscribeHandler.java | 13 +-- .../rocketmq/mqtt/cs/session/CoapSession.java | 82 +++++++++++++++++-- .../mqtt/cs/session/infly/PushAction.java | 57 ++++++++++++- .../mqtt/cs/session/loop/CoapSessionLoop.java | 3 +- .../cs/session/loop/CoapSessionLoopImpl.java | 28 +++++-- .../mqtt/cs/session/match/MatchAction.java | 29 +++++++ .../session/notify/MessageNotifyAction.java | 23 ++++++ 8 files changed, 216 insertions(+), 21 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 3f78871bb..3e3a5a9f1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -60,6 +60,8 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List { @@ -108,7 +111,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, return; } // todo: removeFuture - doResponseSuccess(ctx, coapMessage); + doResponseSuccess(ctx, coapMessage, session); // todo: sendRetainMessage() @@ -148,7 +151,7 @@ private byte[] intToByteArray(int value) { return byteArray; } - public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, CoapSession session) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, @@ -159,7 +162,7 @@ public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coap "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), coapMessage.getRemoteAddress() ); - response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(1))); + response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(session.getMessageNum()))); if (ctx.channel().isActive()) { ctx.writeAndFlush(response); } else { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 485f3b100..30b0bba8a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -17,23 +17,21 @@ package org.apache.rocketmq.mqtt.cs.session; -import org.apache.rocketmq.mqtt.common.model.Message; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.model.*; import org.apache.rocketmq.mqtt.common.model.Queue; -import org.apache.rocketmq.mqtt.common.model.QueueOffset; -import org.apache.rocketmq.mqtt.common.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; -import java.util.Map; -import java.util.LinkedHashSet; -import java.util.Set; -import java.util.List; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; public class CoapSession { private static Logger logger = LoggerFactory.getLogger(CoapSession.class); private InetSocketAddress address; + private ChannelHandlerContext ctx; private int messageId; private byte[] token; private int messageNum = 0; @@ -151,6 +149,68 @@ public boolean sendingMessageIsEmpty(Queue queue) { } } + public List pendMessageList(Queue queue) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + List list = new ArrayList<>(); + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return null; + } + synchronized (this) { + if (!messages.isEmpty()) { + for (Message message : messages) { + if (message.getAck() == -1) { + list.add(message); + } + } + } + } + return list; + } + + public Message nextSendMessageByOrder(Queue queue) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return null; + } + synchronized (this) { + return messages.isEmpty() ? null : messages.iterator().next(); + } + } + + public void sendNewMessage(byte[] payload, int qos) { + this.messageNum++; + CoapMessage data = new CoapMessage( + Constants.COAP_VERSION, + qos == 0 ? CoapMessageType.NON : CoapMessageType.CON, + this.token.length, + CoapMessageCode.CONTENT, + this.messageId + this.messageNum, + this.token, + payload, + this.address + ); + data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); + if (this.ctx.channel().isActive()) { + this.ctx.writeAndFlush(data); + } else { + System.out.println("Channel is not active"); + } + } + + private byte[] intToByteArray(int value) { + byte[] byteArray = new byte[3]; + byteArray[0] = (byte) (value >> 16); + byteArray[1] = (byte) (value >> 8); + byteArray[2] = (byte) (value); + return byteArray; + } + public InetSocketAddress getAddress() { return address; } @@ -207,6 +267,14 @@ public void setPullSize(int pullSize) { this.pullSize = pullSize; } + public ChannelHandlerContext getCtx() { + return ctx; + } + + public void setCtx(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + public Map getOffsetMap() { return offsetMap; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 235658704..e6f95f782 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -27,20 +27,20 @@ import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; -import org.apache.rocketmq.mqtt.common.model.Message; -import org.apache.rocketmq.mqtt.common.model.Queue; -import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.apache.rocketmq.mqtt.common.model.*; import org.apache.rocketmq.mqtt.common.util.MessageUtil; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.channel.ChannelInfo; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.mqtt.facotry.MqttMessageFactory; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.Session; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; @@ -95,6 +95,26 @@ public void messageArrive(Session session, Subscription subscription, Queue queu } } + public void coapMessageArrive(CoapSession session, Queue queue) { + if (session == null) { + return; + } + if (!connectConf.isOrder()) { + List list = session.pendMessageList(queue); + if (list != null && !list.isEmpty()) { + for (Message message : list) { + message.setAck(0); + coapPush(message, session, queue); + } + } + return; + } + Message message = session.nextSendMessageByOrder(queue); + if (message != null) { + coapPush(message, session, queue); + } + } + public void push(Message message, Subscription subscription, Session session, Queue queue) { String clientId = session.getClientId(); int mqttId = mqttMsgId.nextId(clientId); @@ -132,6 +152,37 @@ public void push(Message message, Subscription subscription, Session session, Qu } } + public void coapPush(Message message, CoapSession session, Queue queue) { + try { + if (message.getStoreTimestamp() > 0 && message.getStoreTimestamp() < session.getSubscribeTime()) { + logger.warn("coap old msg:{},{},{},{}", session.getAddress(), message.getMsgId(), + message.getStoreTimestamp(), session.getSubscribeTime()); +// rollNext(session); + return; + } + } catch (Exception e) { + logger.error("", e); + } + + //deal with message with empty payload + String msgPayLoad = new String(message.getPayload()); + if (msgPayLoad.equals(MessageUtil.EMPTYSTRING) && message.isEmpty()) { + message.setPayload("".getBytes()); + } + + Subscription subscription = session.getSubscription(); + int qos = subscription.getQos(); + if (message.qos() != null && (subscription.isP2p() || message.qos() < qos)) { + qos = message.qos(); + } + if (qos == 0) { + session.sendNewMessage(message.getPayload(), qos); +// rollNextByAck(session); + } else { + // todo: deal with qos 1/2 + } + } + public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { String payLoad = new String(message.getPayload()); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java index 0b1b4069a..1b1ababd0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.mqtt.cs.session.loop; +import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import java.net.InetSocketAddress; @@ -30,5 +31,5 @@ public interface CoapSessionLoop { CoapSession removeSession(InetSocketAddress address); - void notifyPullMessage(); + void notifyPullMessage(CoapSession session, Queue queue); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index e11e4ceb3..f70cbdc5f 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -28,6 +28,7 @@ import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.QueueFresh; import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.infly.PushAction; import org.apache.rocketmq.mqtt.cs.session.match.MatchAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,6 +50,9 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ private static Logger logger = LoggerFactory.getLogger(CoapSessionLoopImpl.class); + @Resource + private PushAction pushAction; + @Resource private MatchAction matchAction; @@ -142,8 +146,13 @@ public CoapSession removeSession(InetSocketAddress address) { } @Override - public void notifyPullMessage() { - + public void notifyPullMessage(CoapSession session, Queue queue) { + if (session == null || queue == null) { + return; + } + logger.info("session loop impl doing notifyPullMessage queueFresh.freshQueue({}, {}})", session, session.getSubscription()); + queueFresh.freshQueue(session); + pullMessage(session, queue); } private void pullMessage(CoapSession session, Queue queue) { @@ -194,9 +203,9 @@ private void doPull(PullEvent pullEvent) { scheduler.schedule(() -> pullMessage(session, queue), pullIntervalMillis, TimeUnit.MILLISECONDS); } boolean add = session.addSendingMessages(queue, pullResult.getMessageList()); -// if (add) { -// pushAction.messageArrive(session, subscription, queue); -// } + if (add) { + pushAction.coapMessageArrive(session, queue); + } } else if (PullResult.PULL_OFFSET_MOVED == pullResult.getCode()) { queueOffset.setOffset(pullResult.getNextQueueOffset().getOffset()); // session.markPersistOffsetFlag(true); @@ -208,6 +217,15 @@ private void doPull(PullEvent pullEvent) { clearPullStatus(session, queue, pullEvent); } }); + + CompletableFuture pullResult = lmqQueueStore.pullMessage(subscription.toFirstTopic(), queue, queueOffset, count); + pullResult.whenComplete((pullResult1, throwable) -> { + if (throwable != null) { + result.completeExceptionally(throwable); + } else { + result.complete(pullResult1); + } + }); } private void clearPullStatus(CoapSession session, Queue queue, PullEvent pullEvent) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java index a71207e0b..12f0643a7 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java @@ -25,6 +25,7 @@ import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.apache.rocketmq.mqtt.cs.session.loop.SessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,6 +48,9 @@ public class MatchAction { @Resource private SessionLoop sessionLoop; + @Resource + private CoapSessionLoop coapSessionLoop; + private Trie trie = new Trie<>(); private ConcurrentMap> topicCache = new ConcurrentHashMap<>(16); @@ -107,6 +111,31 @@ public Set> matchClients(String topic, String namesp return result; } + public Set matchCoapClients(String topic) { + Set result = new HashSet<>(); + MqttTopic mqttTopic = TopicUtils.decode(topic); + String secondTopic = TopicUtils.normalizeSecondTopic(mqttTopic.getSecondTopic()); + if (TopicUtils.isRetryTopic(topic) || TopicUtils.isP2P(secondTopic) || TopicUtils.isP2pTopic(topic)) { + return result; + } + Set addresses = new HashSet<>(); + synchronized (coapTopicCache) { + Set precises = coapTopicCache.get(topic); + if (precises != null && !precises.isEmpty()) { + addresses.addAll(precises); + } + } + // todo: add trie relative + for (InetSocketAddress address : addresses) { + CoapSession session = coapSessionLoop.getSession(address); + if (session == null) { + continue; + } + result.add(session); + } + return result; + } + public void addSubscription(Session session, Set subscriptions) { String channelId = session.getChannelId(); if (channelId == null || subscriptions == null || subscriptions.isEmpty()) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java index e1f0ec3c2..3f93e16b9 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java @@ -22,8 +22,10 @@ import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.util.TopicUtils; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.QueueFresh; import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.apache.rocketmq.mqtt.cs.session.loop.QueueCache; import org.apache.rocketmq.mqtt.cs.session.loop.SessionLoop; import org.apache.rocketmq.mqtt.cs.session.match.MatchAction; @@ -46,6 +48,9 @@ public class MessageNotifyAction { @Resource private SessionLoop sessionLoop; + @Resource + private CoapSessionLoop coapSessionLoop; + @Resource private QueueCache queueCache; @@ -77,6 +82,24 @@ public void notify(List events) { } } } + for (MessageEvent event : events) { + Set coapResult = matchAction.matchCoapClients(TopicUtils.normalizeTopic(event.getPubTopic())); + if (coapResult == null || coapResult.isEmpty()) { + continue; + } + for (CoapSession coapSession : coapResult) { + Set set = queueFresh.freshQueue(coapSession); + if (set == null || set.isEmpty()) { + continue; + } + for (Queue queue : set) { + if (isTargetQueue(queue, event)) { + // todo: add queueCache + coapSessionLoop.notifyPullMessage(coapSession, queue); + } + } + } + } } private boolean isTargetQueue(Queue queue, MessageEvent event) { From b19e62317f527ec532007e85c8cb10a9011d9104 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 30 Jul 2024 20:08:20 +0800 Subject: [PATCH 15/67] CoapSession relative, complete basic subscribe and publish, can send message successfully. --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 2 - .../rocketmq/mqtt/cs/session/CoapSession.java | 57 ++++++++++++++++++- .../mqtt/cs/session/infly/PushAction.java | 12 +++- .../cs/session/loop/CoapSessionLoopImpl.java | 1 - 4 files changed, 66 insertions(+), 6 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 3e3a5a9f1..3f78871bb 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -60,8 +60,6 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List pendMessageList(Queue queue) { return list; } + public void ack(Queue pendingQueue, long pendingDownSeqId) { + if (pendingQueue == null) { + throw new RuntimeException("queue is null"); + } + LinkedHashSet messages = sendingMessages.get(pendingQueue); + if (messages == null) { + return; + } + synchronized (this) { + if (messages.isEmpty()) { + return; + } + boolean flag = true; + Iterator iterator = messages.iterator(); + while (iterator.hasNext()) { + Message message = iterator.next(); + if (message.getOffset() == pendingDownSeqId) { + message.setAck(1); + } + if (message.getAck() != 1) { + flag = false; + } + if (flag) { + updateQueueOffset(pendingQueue, message); +// this.markPersistOffsetFlag(true); + iterator.remove(); + } + } + } + } + public Message nextSendMessageByOrder(Queue queue) { if (queue == null) { throw new RuntimeException("queue is null"); @@ -183,7 +214,7 @@ public Message nextSendMessageByOrder(Queue queue) { } } - public void sendNewMessage(byte[] payload, int qos) { + public void sendNewMessage(Queue queue, Message messageSend, int qos) { this.messageNum++; CoapMessage data = new CoapMessage( Constants.COAP_VERSION, @@ -192,7 +223,7 @@ public void sendNewMessage(byte[] payload, int qos) { CoapMessageCode.CONTENT, this.messageId + this.messageNum, this.token, - payload, + messageSend.getPayload(), this.address ); data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); @@ -201,6 +232,28 @@ public void sendNewMessage(byte[] payload, int qos) { } else { System.out.println("Channel is not active"); } + if (qos == 0) { + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return; + } + synchronized (this) { + if (messages.isEmpty()) { + return; + } + Iterator iterator = messages.iterator(); + while (iterator.hasNext()) { + Message message = iterator.next(); + if (message.equals(messageSend)) { + message.setAck(1); + } + if (message.getAck() == 1) { + updateQueueOffset(queue, message); + iterator.remove(); + } + } + } + } } private byte[] intToByteArray(int value) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index e6f95f782..8f4cee3f6 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -176,8 +176,18 @@ public void coapPush(Message message, CoapSession session, Queue queue) { qos = message.qos(); } if (qos == 0) { - session.sendNewMessage(message.getPayload(), qos); + session.sendNewMessage(queue, message, qos); +// session.updateQueueOffset(queue, message); // rollNextByAck(session); +// if (!connectConf.isOrder()) { +// session.ack(pendingQueue, pendingDownSeqId); +// return; +// } +// +// Message nextSendOne = session.rollNext(subscription, pendingQueue, pendingDownSeqId); +// if (nextSendOne != null) { +// push(nextSendOne, subscription, session, pendingQueue); +// } } else { // todo: deal with qos 1/2 } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index f70cbdc5f..206f5a269 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -27,7 +27,6 @@ import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.QueueFresh; -import org.apache.rocketmq.mqtt.cs.session.Session; import org.apache.rocketmq.mqtt.cs.session.infly.PushAction; import org.apache.rocketmq.mqtt.cs.session.match.MatchAction; import org.slf4j.Logger; From f3281541f95693106c8cfd2e73793e8d1582f2fb Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 31 Jul 2024 16:25:13 +0800 Subject: [PATCH 16/67] Add HashedWheeltimer in CoapSessionLoopImpl to check session alive periodly, and remove the session which is expired. --- .../rocketmq/mqtt/cs/config/ConnectConf.java | 10 ++++++++ .../cs/session/loop/CoapSessionLoopImpl.java | 24 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java index aab2a1f94..b20858ec6 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java @@ -83,6 +83,8 @@ public class ConnectConf { private int serverReceiveMaximum = 32767; private int maxTransferCountOnMessageInDisk = 8; + private long coapSessionTimeout = 90000; //90000ms,90s + public ConnectConf() throws IOException { ClassPathResource classPathResource = new ClassPathResource(CONF_FILE_NAME); InputStream in = classPathResource.getInputStream(); @@ -364,4 +366,12 @@ public boolean isEnableQuic() { public void setEnableQuic(boolean enableQuic) { this.enableQuic = enableQuic; } + + public long getCoapSessionTimeout() { + return coapSessionTimeout; + } + + public void setCoapSessionTimeout(long coapSessionTimeout) { + this.coapSessionTimeout = coapSessionTimeout; + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 206f5a269..7a67512d9 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -18,6 +18,8 @@ package org.apache.rocketmq.mqtt.cs.session.loop; import com.alibaba.fastjson.JSONObject; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timeout; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; import org.apache.rocketmq.mqtt.common.model.PullResult; @@ -67,6 +69,7 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ private ScheduledThreadPoolExecutor pullService; private ScheduledThreadPoolExecutor scheduler; + private HashedWheelTimer hashedWheelTimer; private Map sessionMap = new ConcurrentHashMap<>(1024); private Map pullEventMap = new ConcurrentHashMap<>(1024); @@ -80,6 +83,13 @@ public void init() { pullService = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_pull_message_thread_")); scheduler = new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("coap_loop_scheduler_")); pullService.scheduleWithFixedDelay(() -> pullLoop(), pullIntervalMillis, pullIntervalMillis, TimeUnit.MILLISECONDS); + hashedWheelTimer = new HashedWheelTimer(1, TimeUnit.SECONDS); + hashedWheelTimer.start(); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + for (InetSocketAddress address : sessionMap.keySet()) { + removeSession(address); + } + })); } private void pullLoop() { @@ -119,6 +129,7 @@ public void addSession(CoapSession session, CompletableFuture future) { initOffset(session, entry.getKey(), entry.getValue(), future, result); } matchAction.addSubscription(session); + hashedWheelTimer.newTimeout(timeout -> checkSessionAlive(timeout, address), connectConf.getCoapSessionTimeout(), TimeUnit.MILLISECONDS); } @Override @@ -169,6 +180,19 @@ private void pullMessage(CoapSession session, Queue queue) { } } + private void checkSessionAlive(Timeout timeout, InetSocketAddress address) { + CoapSession session = sessionMap.get(address); + if (session == null) { + return; + } + if (System.currentTimeMillis() - session.getSubscribeTime() > connectConf.getCoapSessionTimeout()) { + removeSession(address); + } else { + long delay = connectConf.getCoapSessionTimeout() - (System.currentTimeMillis() - session.getSubscribeTime()); + hashedWheelTimer.newTimeout(timeout.task(), delay, TimeUnit.MILLISECONDS); + } + } + private void doPull(PullEvent pullEvent) { CoapSession session = pullEvent.session; Subscription subscription = session.getSubscription(); From a45d2e9ede5cedda4eb68934253caf75585b396d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 31 Jul 2024 16:38:41 +0800 Subject: [PATCH 17/67] Complete: send final message when removing the session. --- .../rocketmq/mqtt/cs/session/CoapSession.java | 21 +++++++++++++++++++ .../cs/session/loop/CoapSessionLoopImpl.java | 1 + 2 files changed, 22 insertions(+) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index ff8641222..db4d9bd24 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; @@ -256,6 +257,26 @@ public void sendNewMessage(Queue queue, Message messageSend, int qos) { } } + public void sendRemoveSessionMessage() { + this.messageNum++; + CoapMessage data = new CoapMessage( + Constants.COAP_VERSION, + this.subscription.getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, + this.token.length, + CoapMessageCode.FORBIDDEN, + this.messageId + this.messageNum, + this.token, + "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8), + this.address + ); + data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); + if (this.ctx.channel().isActive()) { + this.ctx.writeAndFlush(data); + } else { + System.out.println("Channel is not active"); + } + } + private byte[] intToByteArray(int value) { byte[] byteArray = new byte[3]; byteArray[0] = (byte) (value >> 16); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 7a67512d9..824729ef7 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -146,6 +146,7 @@ public CoapSession removeSession(InetSocketAddress address) { } // todo: inFlyCache.cleanResource() if (session != null) { + session.sendRemoveSessionMessage(); matchAction.removeSubscription(session); // todo: persistOffset(session) } From 3cffd375b4f932598f0c6f657a3a691ef013ab77 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 31 Jul 2024 17:30:58 +0800 Subject: [PATCH 18/67] Complete: store retain message, send retain message when subscribe. --- .../coap/handler/CoapSubscribeHandler.java | 22 +++++++++++-- .../rocketmq/mqtt/cs/session/CoapSession.java | 31 +++++++------------ .../coap/processor/CoapPublishProcessor.java | 3 ++ 3 files changed, 34 insertions(+), 22 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 761881c0b..a7bb6ba96 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -19,13 +19,13 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.mqtt.common.facade.RetainedPersistManager; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.*; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; -import org.apache.rocketmq.mqtt.cs.protocol.mqtt.handler.MqttSubscribeHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; -import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.infly.PushAction; import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +45,12 @@ public class CoapSubscribeHandler implements CoapPacketHandler retainedMessage = retainedPersistManager.getRetainedMessage(session.getSubscription().getTopicFilter()); + retainedMessage.whenComplete(((message, throwable) -> { + if (message == null) { + return; + } + session.write(message.getPayload()); + })); + } + public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, String errContent) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index db4d9bd24..2d48e9ab3 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -216,23 +216,7 @@ public Message nextSendMessageByOrder(Queue queue) { } public void sendNewMessage(Queue queue, Message messageSend, int qos) { - this.messageNum++; - CoapMessage data = new CoapMessage( - Constants.COAP_VERSION, - qos == 0 ? CoapMessageType.NON : CoapMessageType.CON, - this.token.length, - CoapMessageCode.CONTENT, - this.messageId + this.messageNum, - this.token, - messageSend.getPayload(), - this.address - ); - data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); - if (this.ctx.channel().isActive()) { - this.ctx.writeAndFlush(data); - } else { - System.out.println("Channel is not active"); - } + write(messageSend.getPayload()); if (qos == 0) { LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { @@ -258,15 +242,24 @@ public void sendNewMessage(Queue queue, Message messageSend, int qos) { } public void sendRemoveSessionMessage() { + byte[] payload = "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8); + write(payload, CoapMessageCode.FORBIDDEN); + } + + public void write(byte[] payload) { + write(payload, CoapMessageCode.CONTENT); + } + + public void write(byte[] payload, CoapMessageCode code) { this.messageNum++; CoapMessage data = new CoapMessage( Constants.COAP_VERSION, this.subscription.getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, this.token.length, - CoapMessageCode.FORBIDDEN, + code, this.messageId + this.messageNum, this.token, - "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8), + payload, this.address ); data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index 5bad78faa..acbdbb905 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -49,7 +49,10 @@ public CompletableFuture process(CoapRequestMessage msg) { } public CompletableFuture put(CoapRequestMessage coapMessage) { + // todo: process topic alias + boolean isEmpty = false; + // deal empty payload if (coapMessage.getPayload() == null || coapMessage.getPayload().length == 0) { coapMessage.setPayload(MessageUtil.EMPTYSTRING.getBytes(StandardCharsets.UTF_8)); From b72774e8ab6491c279c31a6f6d9c79c8e6155404 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 1 Aug 2024 17:05:29 +0800 Subject: [PATCH 19/67] Add RPC forward for coap when receiving datagram packet. But need further process for RPC reception. --- .../rocketmq/mqtt/common/model/RpcCode.java | 2 + .../mqtt/cs/protocol/coap/CoapDecoder.java | 3 - .../mqtt/cs/protocol/coap/CoapRPCHandler.java | 76 +++++++++++++++++++ .../cs/protocol/rpc/RpcPacketDispatcher.java | 24 ++++++ .../rocketmq/mqtt/cs/starter/MqttServer.java | 6 +- .../mqtt/ds/notify/NotifyManager.java | 38 ++++++++++ 6 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/RpcCode.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/RpcCode.java index d08d36a17..326cbfdef 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/RpcCode.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/RpcCode.java @@ -23,4 +23,6 @@ public class RpcCode { public static final int CMD_NOTIFY_MQTT_MESSAGE = 201; public static final int CMD_CLOSE_CHANNEL = 203; + + public static final int COM_NOTIFY_COAP_MESSAGE = 301; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 3f78871bb..83d5fe96b 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -16,7 +16,6 @@ */ package org.apache.rocketmq.mqtt.cs.protocol.coap; -import com.sun.org.apache.bcel.internal.Const; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramPacket; @@ -34,9 +33,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java new file mode 100644 index 000000000..77099ba88 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java @@ -0,0 +1,76 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.channel.socket.DatagramPacket; +import io.netty.util.ReferenceCountUtil; +import org.apache.rocketmq.mqtt.common.facade.MetaPersistManager; +import org.apache.rocketmq.mqtt.ds.notify.NotifyManager; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.Set; +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; + +@Component +public class CoapRPCHandler extends SimpleChannelInboundHandler { + + @Resource + private MetaPersistManager metaPersistManager; + + @Resource + private NotifyManager notifyManager; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception { + // get sender socket address + InetSocketAddress address = packet.sender(); + // get machines + Set connectorNodes = metaPersistManager.getConnectNodeSet(); + if (connectorNodes == null || connectorNodes.isEmpty()) { + throw new RemotingException("No Connect Nodes"); + } + // not forward if the packet is from RPC + if (connectorNodes.contains(address.getHostName())) { + ctx.fireChannelRead(packet); + } + // calculate machine index to forward + int hash = address.toString().hashCode(); + int nodeNum = Math.abs(hash % connectorNodes.size()); + List nodeList = new ArrayList<>(connectorNodes); + Collections.sort(nodeList); + String forwardNode = nodeList.get(nodeNum); + // forward the packet if not for localhost + if (InetAddress.getLocalHost().getHostAddress().equals(forwardNode)) { + ctx.fireChannelRead(packet); + } else { + try { + notifyManager.doCoapForward(forwardNode, packet); + } finally { + ReferenceCountUtil.release(packet); + } + } + } +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java index ccea43bcc..043004e85 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java @@ -18,8 +18,12 @@ package org.apache.rocketmq.mqtt.cs.protocol.rpc; +import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; import org.apache.rocketmq.mqtt.common.model.MessageEvent; import org.apache.rocketmq.mqtt.common.model.RpcCode; import org.apache.rocketmq.mqtt.common.model.RpcHeader; @@ -32,6 +36,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.List; @@ -56,6 +61,8 @@ public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand doNotify(request); } else if (RpcCode.CMD_CLOSE_CHANNEL == code) { closeChannel(request); + } else if (RpcCode.COM_NOTIFY_COAP_MESSAGE == code) { + doNotifyCoap(request); } } catch (Throwable t) { logger.error("", t); @@ -81,4 +88,21 @@ private void closeChannel(RemotingCommand request) { channelManager.closeConnect(channelId, request.getRemark()); } + private void doNotifyCoap(RemotingCommand request) { + String payload = new String(request.getBody(), StandardCharsets.UTF_8); + JSONObject jsonObject = JSON.parseObject(payload); + + byte[] data = jsonObject.getBytes("data"); + String senderAddress = jsonObject.getString("senderAddress"); + int senderPort = jsonObject.getIntValue("senderPort"); + String recipientAddress = jsonObject.getString("recipientAddress"); + int recipientPort = jsonObject.getIntValue("recipientPort"); + ByteBuf buffer = Unpooled.wrappedBuffer(data); + + InetSocketAddress sender = new InetSocketAddress(senderAddress, senderPort); + InetSocketAddress recipient = new InetSocketAddress(recipientAddress, recipientPort); + DatagramPacket packet = new DatagramPacket(buffer.retain(), recipient, sender); + System.out.println(packet); + } + } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 8d33db048..7ed1c69e3 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -51,6 +51,7 @@ import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.ChannelPipelineLazyInit; import org.apache.rocketmq.mqtt.cs.protocol.MqttVersionHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapRPCHandler; import org.apache.rocketmq.mqtt.cs.protocol.mqtt.MqttPacketDispatcher; import org.apache.rocketmq.mqtt.cs.protocol.mqtt5.Mqtt5PacketDispatcher; import org.apache.rocketmq.mqtt.cs.protocol.ssl.SslFactory; @@ -92,6 +93,9 @@ public class MqttServer { @Resource private Mqtt5PacketDispatcher mqtt5PacketDispatcher; + @Resource + private CoapRPCHandler coapRPCHandler; + @Resource private CoapPacketDispatcher coapPacketDispatcher; @@ -315,7 +319,7 @@ private void startCoap() { @Override protected void initChannel(DatagramChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); -// pipeline.addLast("coap-handler", new CoapHandler()); + pipeline.addLast("coap-handler", coapRPCHandler); pipeline.addLast("coap-encoder", new CoapEncoder()); pipeline.addLast("coap-decoder", new CoapDecoder()); pipeline.addLast("coap-dispatcher", coapPacketDispatcher); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java index d94b0f6b8..204591171 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java @@ -18,6 +18,9 @@ package org.apache.rocketmq.mqtt.ds.notify; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import io.netty.buffer.ByteBuf; +import io.netty.channel.socket.DatagramPacket; import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer; import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext; @@ -50,6 +53,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.net.InetSocketAddress; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -270,6 +274,40 @@ protected boolean doNotify(String node, Set messageEvents) { } } + public boolean doCoapForward(String node, DatagramPacket packet) { + Set connectorNodes = metaPersistManager.getConnectNodeSet(); + if (connectorNodes == null || connectorNodes.isEmpty()) { + return false; + } + if (!connectorNodes.contains(node)) { + return true; + } + try { + // serialization + ByteBuf buffer = packet.content(); + byte[] data = new byte[buffer.readableBytes()]; + buffer.getBytes(buffer.readerIndex(), data); + InetSocketAddress sender = packet.sender(); + InetSocketAddress recipient = packet.recipient(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("data", data); + jsonObject.put("senderAddress", sender.getAddress().getHostAddress()); + jsonObject.put("senderPort", sender.getPort()); + jsonObject.put("recipientAddress", recipient.getAddress().getHostAddress()); + jsonObject.put("recipientPort", recipient.getPort()); + // create command + RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RpcCode.COM_NOTIFY_COAP_MESSAGE, + null); + remotingCommand.setBody(JSON.toJSONBytes(jsonObject)); + // RPC invoke + RemotingCommand response = remotingClient.invokeSync(node + ":" + serviceConf.getCsRpcPort(), remotingCommand, 1000); + return response.getCode() == RpcCode.SUCCESS; + } catch (Exception e) { + logger.error("fail coap forward {}", node, e); + return false; + } + } + private RemotingCommand createMsgEventCommand(Set messageEvents) { RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RpcCode.CMD_NOTIFY_MQTT_MESSAGE, null); From 67c0cf9a165815999c4c94c1e17adc625ee94f28 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 1 Aug 2024 17:24:39 +0800 Subject: [PATCH 20/67] code formatting. --- distribution/conf/service.conf | 2 +- .../mqtt/common/model/CoapRequestMessage.java | 16 ++++++++++++++ .../coap/handler/CoapConnectHandler.java | 16 ++++++++++++++ .../coap/handler/CoapDisconnectHandler.java | 16 ++++++++++++++ .../coap/handler/CoapHeartbeatHandler.java | 16 ++++++++++++++ .../coap/handler/CoapPublishHandler.java | 22 ++++++++++++++++++- .../coap/handler/CoapSubscribeHandler.java | 10 ++++++++- .../cs/protocol/rpc/RpcPacketDispatcher.java | 6 ++--- .../rocketmq/mqtt/cs/session/CoapSession.java | 18 ++++++++++++--- .../mqtt/cs/session/infly/PushAction.java | 7 +++--- .../mqtt/cs/session/match/MatchAction.java | 6 ++--- .../session/notify/MessageNotifyAction.java | 4 ++-- .../rocketmq/mqtt/cs/starter/MqttServer.java | 4 ++-- .../mqtt/ds/notify/NotifyManager.java | 2 +- .../coap/processor/CoapConnectProcessor.java | 16 ++++++++++++++ .../processor/CoapDisconnectProcessor.java | 16 ++++++++++++++ .../processor/CoapHeartbeatProcessor.java | 16 ++++++++++++++ .../coap/processor/CoapPublishProcessor.java | 16 ++++++++++++++ 18 files changed, 189 insertions(+), 20 deletions(-) diff --git a/distribution/conf/service.conf b/distribution/conf/service.conf index 767070344..07dd285ac 100644 --- a/distribution/conf/service.conf +++ b/distribution/conf/service.conf @@ -21,4 +21,4 @@ NAMESRV_ADDR=11.159.23.108:9876 eventNotifyRetryTopic=eventNotifyRetryTopic clientRetryTopic=clientRetryTopic -metaAddr=11.159.23.108:25000,11.159.23.111:25000,11.159.23.104:25000 +metaAddr=11.159.23.108:25000,11.159.23.111:25000,11.159.23.104:25000 \ No newline at end of file diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java index 21846a949..086b8caea 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestMessage.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.model; import io.netty.handler.codec.mqtt.MqttQoS; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index c1fdf1da5..cdd7d9427 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index 06b2455ab..053f0024e 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java index fa4b824b3..b47348ad8 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java index 16d96dee2..da9ac96c6 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -1,8 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.springframework.stereotype.Component; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index a7bb6ba96..40ec6bc15 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -21,7 +21,15 @@ import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.mqtt.common.facade.RetainedPersistManager; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.Message; +import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java index 043004e85..701e27e83 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java @@ -18,11 +18,11 @@ package org.apache.rocketmq.mqtt.cs.protocol.rpc; -import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; +import com.alibaba.fastjson.JSON; +import io.netty.channel.ChannelHandlerContext; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; -import io.netty.channel.ChannelHandlerContext; import io.netty.channel.socket.DatagramPacket; import org.apache.rocketmq.mqtt.common.model.MessageEvent; import org.apache.rocketmq.mqtt.common.model.RpcCode; @@ -36,9 +36,9 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.List; +import java.net.InetSocketAddress; @Component diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 2d48e9ab3..fe3720b04 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -17,16 +17,28 @@ package org.apache.rocketmq.mqtt.cs.session; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.Message; +import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.Queue; +import org.apache.rocketmq.mqtt.common.model.QueueOffset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Set; +import java.util.LinkedHashSet; +import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; public class CoapSession { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 8f4cee3f6..673e77fec 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -27,20 +27,21 @@ import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Message; +import org.apache.rocketmq.mqtt.common.model.Queue; +import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.util.MessageUtil; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.channel.ChannelInfo; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.mqtt.facotry.MqttMessageFactory; -import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.Session; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java index 12f0643a7..5b0d97cce 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/match/MatchAction.java @@ -23,22 +23,22 @@ import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.model.Trie; import org.apache.rocketmq.mqtt.common.util.TopicUtils; -import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.Session; -import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.loop.SessionLoop; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.net.InetSocketAddress; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.net.InetSocketAddress; @Component diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java index 3f93e16b9..314ec607c 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/notify/MessageNotifyAction.java @@ -22,12 +22,12 @@ import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.util.TopicUtils; -import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.QueueFresh; import org.apache.rocketmq.mqtt.cs.session.Session; -import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.loop.QueueCache; import org.apache.rocketmq.mqtt.cs.session.loop.SessionLoop; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.apache.rocketmq.mqtt.cs.session.match.MatchAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 7ed1c69e3..1a0c4ae86 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -51,16 +51,16 @@ import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.ChannelPipelineLazyInit; import org.apache.rocketmq.mqtt.cs.protocol.MqttVersionHandler; -import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapRPCHandler; import org.apache.rocketmq.mqtt.cs.protocol.mqtt.MqttPacketDispatcher; import org.apache.rocketmq.mqtt.cs.protocol.mqtt5.Mqtt5PacketDispatcher; import org.apache.rocketmq.mqtt.cs.protocol.ssl.SslFactory; import org.apache.rocketmq.mqtt.cs.protocol.ws.WebSocketServerHandler; import org.apache.rocketmq.mqtt.cs.protocol.ws.WebSocketEncoder; -import org.apache.rocketmq.remoting.common.TlsMode; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapRPCHandler; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapDecoder; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapEncoder; import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapPacketDispatcher; +import org.apache.rocketmq.remoting.common.TlsMode; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java index 204591171..096b2df89 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java @@ -53,7 +53,6 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; -import java.net.InetSocketAddress; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -63,6 +62,7 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.net.InetSocketAddress; @Component diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java index 2d6b09c9c..cd93a767f 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java index 7df83d876..00fc1fc0c 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapDisconnectProcessor.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java index 96e6b28f8..17056de80 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapHeartbeatProcessor.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index acbdbb905..d46c184e7 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -1,3 +1,19 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; import com.alibaba.fastjson.JSON; From d32b4522dd20739a23d346adc6af2ac2e9292c9f Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 1 Aug 2024 19:23:17 +0800 Subject: [PATCH 21/67] Complete RPC forward. --- .../cs/channel/DatagramChannelManager.java | 41 +++++++++++++++++++ .../mqtt/cs/protocol/coap/CoapRPCHandler.java | 4 -- .../cs/protocol/rpc/RpcPacketDispatcher.java | 6 ++- .../rocketmq/mqtt/cs/starter/MqttServer.java | 2 + 4 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java new file mode 100644 index 000000000..b3639ac44 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.channel; + +import io.netty.channel.socket.DatagramChannel; + +public class DatagramChannelManager { + private static DatagramChannelManager instance; + private DatagramChannel datagramChannel; + + private DatagramChannelManager() {} + + public static synchronized DatagramChannelManager getInstance() { + if (instance == null) { + instance = new DatagramChannelManager(); + } + return instance; + } + + public void setDatagramChannel(DatagramChannel channel) { + this.datagramChannel = channel; + } + + public DatagramChannel getDatagramChannel() { + return datagramChannel; + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java index 77099ba88..cd26ce24a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java @@ -52,10 +52,6 @@ protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) th if (connectorNodes == null || connectorNodes.isEmpty()) { throw new RemotingException("No Connect Nodes"); } - // not forward if the packet is from RPC - if (connectorNodes.contains(address.getHostName())) { - ctx.fireChannelRead(packet); - } // calculate machine index to forward int hash = address.toString().hashCode(); int nodeNum = Math.abs(hash % connectorNodes.size()); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java index 701e27e83..429c3a98a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/rpc/RpcPacketDispatcher.java @@ -23,11 +23,13 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.channel.socket.DatagramChannel; import io.netty.channel.socket.DatagramPacket; import org.apache.rocketmq.mqtt.common.model.MessageEvent; import org.apache.rocketmq.mqtt.common.model.RpcCode; import org.apache.rocketmq.mqtt.common.model.RpcHeader; import org.apache.rocketmq.mqtt.cs.channel.ChannelManager; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.session.notify.MessageNotifyAction; import org.apache.rocketmq.remoting.netty.NettyRequestProcessor; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @@ -102,7 +104,9 @@ private void doNotifyCoap(RemotingCommand request) { InetSocketAddress sender = new InetSocketAddress(senderAddress, senderPort); InetSocketAddress recipient = new InetSocketAddress(recipientAddress, recipientPort); DatagramPacket packet = new DatagramPacket(buffer.retain(), recipient, sender); - System.out.println(packet); + + DatagramChannel channel = DatagramChannelManager.getInstance().getDatagramChannel(); + channel.pipeline().context("coap-handler").fireChannelRead(packet); // forward to coap-decoder } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 1a0c4ae86..50d20edde 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -48,6 +48,7 @@ import java.util.concurrent.TimeUnit; import org.apache.rocketmq.mqtt.cs.channel.ConnectHandler; import org.apache.rocketmq.mqtt.cs.channel.AdaptiveTlsHandler; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.ChannelPipelineLazyInit; import org.apache.rocketmq.mqtt.cs.protocol.MqttVersionHandler; @@ -318,6 +319,7 @@ private void startCoap() { .handler(new ChannelInitializer() { @Override protected void initChannel(DatagramChannel ch) throws Exception { + DatagramChannelManager.getInstance().setDatagramChannel(ch); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("coap-handler", coapRPCHandler); pipeline.addLast("coap-encoder", new CoapEncoder()); From d430178d715b510873c7dad78ef652ad77877e25 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 11:33:44 +0800 Subject: [PATCH 22/67] Add CoapRetryManager --- .../cs/session/infly/CoapRetryManager.java | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java new file mode 100644 index 000000000..a2aea8115 --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session.infly; + +import io.netty.channel.socket.DatagramChannel; +import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@Component +public class CoapRetryManager { + + private static Logger logger = LoggerFactory.getLogger(CoapRetryManager.class); + + private ScheduledThreadPoolExecutor scheduler; + + private ConcurrentMap retryMessageMap = new ConcurrentHashMap<>(1024); + + private static final int SCHEDULE_INTERVAL = 1000; + private static final int MAX_RETRY_TIME = 3; + private static final long RETRY_INTERVAL = 3000; + + @PostConstruct + public void init() { + scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_retry_message_thread_")); + scheduler.scheduleWithFixedDelay(() -> doRetry(), SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); + } + + public void addRetryMessage(CoapMessage message) { + retryMessageMap.put(message.getMessageId(), new RetryMessage(message.getMessageId(), message)); + } + + public void removeRetryMessage(int messageId) { + retryMessageMap.remove(messageId); + } + + public boolean contains(int messageId) { + return retryMessageMap.containsKey(messageId); + } + + private void doRetry() { + if (retryMessageMap.isEmpty()) { + return; + } + long currentTime = System.currentTimeMillis(); + for (RetryMessage retryMessage : retryMessageMap.values()) { + if (currentTime - retryMessage.lastSendTime < RETRY_INTERVAL) { + continue; + } + if (retryMessage.retryTime >= MAX_RETRY_TIME) { + removeRetryMessage(retryMessage.messageId); + logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); + continue; + } + DatagramChannel channel = DatagramChannelManager.getInstance().getDatagramChannel(); + channel.writeAndFlush(retryMessage.message); + retryMessage.retryTime++; + retryMessage.lastSendTime = currentTime; + } + } + + public class RetryMessage { + private int messageId; + private CoapMessage message; + private int retryTime = 0; + private long lastSendTime = System.currentTimeMillis(); + + public RetryMessage(int messageId, CoapMessage message) { + this.messageId = messageId; + this.message = message; + } + } +} From 599d2b244291ae2d00fc51bcd09ee115ede3e400 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 11:45:28 +0800 Subject: [PATCH 23/67] Complete coap retry, but need formatting and removing the write action from session to datagramChannelManager. --- .../rocketmq/mqtt/cs/session/CoapSession.java | 42 +++++++++---------- .../mqtt/cs/session/infly/PushAction.java | 13 +++--- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index fe3720b04..3175351ad 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -227,30 +227,29 @@ public Message nextSendMessageByOrder(Queue queue) { } } - public void sendNewMessage(Queue queue, Message messageSend, int qos) { - write(messageSend.getPayload()); - if (qos == 0) { - LinkedHashSet messages = sendingMessages.get(queue); - if (messages == null) { + public CoapMessage sendNewMessage(Queue queue, Message messageSend, int qos) { + CoapMessage data = write(messageSend.getPayload()); + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return; + } + synchronized (this) { + if (messages.isEmpty()) { return; } - synchronized (this) { - if (messages.isEmpty()) { - return; + Iterator iterator = messages.iterator(); + while (iterator.hasNext()) { + Message message = iterator.next(); + if (message.equals(messageSend)) { + message.setAck(1); } - Iterator iterator = messages.iterator(); - while (iterator.hasNext()) { - Message message = iterator.next(); - if (message.equals(messageSend)) { - message.setAck(1); - } - if (message.getAck() == 1) { - updateQueueOffset(queue, message); - iterator.remove(); - } + if (message.getAck() == 1) { + updateQueueOffset(queue, message); + iterator.remove(); } } } + return data; } public void sendRemoveSessionMessage() { @@ -258,11 +257,11 @@ public void sendRemoveSessionMessage() { write(payload, CoapMessageCode.FORBIDDEN); } - public void write(byte[] payload) { - write(payload, CoapMessageCode.CONTENT); + public CoapMessage write(byte[] payload) { + return write(payload, CoapMessageCode.CONTENT); } - public void write(byte[] payload, CoapMessageCode code) { + public CoapMessage write(byte[] payload, CoapMessageCode code) { this.messageNum++; CoapMessage data = new CoapMessage( Constants.COAP_VERSION, @@ -280,6 +279,7 @@ public void write(byte[] payload, CoapMessageCode code) { } else { System.out.println("Channel is not active"); } + return data; } private byte[] intToByteArray(int value) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 673e77fec..0eeebad9b 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -27,6 +27,7 @@ import org.apache.commons.lang3.StringUtils; import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.common.model.Message; import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.Subscription; @@ -73,6 +74,9 @@ public class PushAction { @Resource private LmqQueueStore lmqQueueStore; + @Resource + private CoapRetryManager coapRetryManager; + public void messageArrive(Session session, Subscription subscription, Queue queue) { if (session == null) { return; @@ -176,8 +180,10 @@ public void coapPush(Message message, CoapSession session, Queue queue) { if (message.qos() != null && (subscription.isP2p() || message.qos() < qos)) { qos = message.qos(); } - if (qos == 0) { - session.sendNewMessage(queue, message, qos); + CoapMessage sendMessage = session.sendNewMessage(queue, message, qos); + if (qos > 0) { + coapRetryManager.addRetryMessage(sendMessage); + } // session.updateQueueOffset(queue, message); // rollNextByAck(session); // if (!connectConf.isOrder()) { @@ -189,9 +195,6 @@ public void coapPush(Message message, CoapSession session, Queue queue) { // if (nextSendOne != null) { // push(nextSendOne, subscription, session, pendingQueue); // } - } else { - // todo: deal with qos 1/2 - } } public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { From 99efdb12d10ac9c0ea1fdf66bda2ff76c67fa537 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 15:17:10 +0800 Subject: [PATCH 24/67] Fix bug: can not receive ack with empty message. And complete retry message removal when receving ack. --- .../mqtt/common/model/CoapMessageCode.java | 8 +++- .../mqtt/common/model/CoapRequestType.java | 12 +++-- .../mqtt/cs/protocol/coap/CoapDecoder.java | 9 +++- .../protocol/coap/CoapPacketDispatcher.java | 15 ++++++ .../protocol/coap/handler/CoapAckHandler.java | 46 +++++++++++++++++++ .../rocketmq/mqtt/cs/session/CoapSession.java | 6 +-- .../mqtt/cs/session/infly/PushAction.java | 37 +++------------ .../coap/processor/CoapAckProcessor.java | 34 ++++++++++++++ 8 files changed, 127 insertions(+), 40 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java index c0d93a04d..22c8b54b8 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessageCode.java @@ -17,6 +17,8 @@ package org.apache.rocketmq.mqtt.common.model; public enum CoapMessageCode { + EMPTY(0), + // Request Code, 0.xx GET(1), POST(2), @@ -62,7 +64,7 @@ public int value() { } public static CoapMessageCode valueOf(int code) { - if (code > 0 && code < VALUES.length && VALUES[code] != null) { + if (code >= 0 && code < VALUES.length && VALUES[code] != null) { return VALUES[code]; } else { throw new IllegalArgumentException("Unknown CoapMessageCode " + code); @@ -72,6 +74,10 @@ public static CoapMessageCode valueOf(int code) { public static boolean isRequestCode(CoapMessageCode code) { return (code == GET) || (code == POST) || (code == PUT) || (code == DELETE); } + + public static boolean isEmptyCode(CoapMessageCode code) { + return code == EMPTY; + } static { CoapMessageCode[] values = values(); diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java index 82e05faae..fefe46e77 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapRequestType.java @@ -17,12 +17,14 @@ package org.apache.rocketmq.mqtt.common.model; public enum CoapRequestType { - PUBLISH(0), - SUBSCRIBE(1), + ACK(0), - CONNECT(2), - HEARTBEAT(3), - DISCONNECT(4); + PUBLISH(1), + SUBSCRIBE(2), + + CONNECT(3), + HEARTBEAT(4), + DISCONNECT(5); private static final CoapRequestType[] VALUES; private final int value; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 83d5fe96b..11b35e957 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -101,7 +101,7 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List processCoapMessage(CoapRequestMessage msg) return coapHeartbeatProcessor.process(msg); case DISCONNECT: return coapDisconnectProcessor.process(msg); + case ACK: + return coapAckProcessor.process(msg); default: } CompletableFuture hookResult = new CompletableFuture<>(); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java new file mode 100644 index 000000000..cb43a569d --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; + +@Component +public class CoapAckHandler implements CoapPacketHandler { + + @Resource + private CoapRetryManager coapRetryManager; + + @Override + public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + return true; + } + + @Override + public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + int messageId = coapMessage.getMessageId(); + if (coapRetryManager.contains(messageId)) { + coapRetryManager.removeRetryMessage(messageId); + } + } +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 3175351ad..f65fed9a8 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -227,15 +227,15 @@ public Message nextSendMessageByOrder(Queue queue) { } } - public CoapMessage sendNewMessage(Queue queue, Message messageSend, int qos) { + public CoapMessage sendNewMessage(Queue queue, Message messageSend) { CoapMessage data = write(messageSend.getPayload()); LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { - return; + return null; } synchronized (this) { if (messages.isEmpty()) { - return; + return null; } Iterator iterator = messages.iterator(); while (iterator.hasNext()) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 0eeebad9b..1f4d472bc 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -104,19 +104,12 @@ public void coapMessageArrive(CoapSession session, Queue queue) { if (session == null) { return; } - if (!connectConf.isOrder()) { - List list = session.pendMessageList(queue); - if (list != null && !list.isEmpty()) { - for (Message message : list) { - message.setAck(0); - coapPush(message, session, queue); - } + List list = session.pendMessageList(queue); + if (list != null && !list.isEmpty()) { + for (Message message : list) { + message.setAck(0); + coapPush(message, session, queue); } - return; - } - Message message = session.nextSendMessageByOrder(queue); - if (message != null) { - coapPush(message, session, queue); } } @@ -162,7 +155,6 @@ public void coapPush(Message message, CoapSession session, Queue queue) { if (message.getStoreTimestamp() > 0 && message.getStoreTimestamp() < session.getSubscribeTime()) { logger.warn("coap old msg:{},{},{},{}", session.getAddress(), message.getMsgId(), message.getStoreTimestamp(), session.getSubscribeTime()); -// rollNext(session); return; } } catch (Exception e) { @@ -176,25 +168,10 @@ public void coapPush(Message message, CoapSession session, Queue queue) { } Subscription subscription = session.getSubscription(); - int qos = subscription.getQos(); - if (message.qos() != null && (subscription.isP2p() || message.qos() < qos)) { - qos = message.qos(); - } - CoapMessage sendMessage = session.sendNewMessage(queue, message, qos); - if (qos > 0) { + CoapMessage sendMessage = session.sendNewMessage(queue, message); + if (subscription.getQos() > 0) { coapRetryManager.addRetryMessage(sendMessage); } -// session.updateQueueOffset(queue, message); -// rollNextByAck(session); -// if (!connectConf.isOrder()) { -// session.ack(pendingQueue, pendingDownSeqId); -// return; -// } -// -// Message nextSendOne = session.rollNext(subscription, pendingQueue, pendingDownSeqId); -// if (nextSendOne != null) { -// push(nextSendOne, subscription, session, pendingQueue); -// } } public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java new file mode 100644 index 000000000..e3593f432 --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java @@ -0,0 +1,34 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.upstream.coap.processor; + +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; +import org.springframework.stereotype.Component; + +import java.util.concurrent.CompletableFuture; + +@Component +public class CoapAckProcessor implements CoapUpstreamProcessor { + + @Override + public CompletableFuture process(CoapRequestMessage coapMessage) { + return HookResult.newHookResult(HookResult.SUCCESS, null, null); + } +} From 32932bddcd406cfc8cc78d48eacf1e5f00ef1598 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 16:42:20 +0800 Subject: [PATCH 25/67] Fix bug: old version will construct a new session each time receiving a subscriptition request. --- .../coap/handler/CoapSubscribeHandler.java | 39 +++++++++++-------- .../rocketmq/mqtt/cs/session/CoapSession.java | 9 ++--- .../cs/session/infly/CoapRetryManager.java | 5 +-- .../cs/session/loop/CoapSessionLoopImpl.java | 1 - 4 files changed, 29 insertions(+), 25 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 40ec6bc15..f5c1aba97 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -40,6 +40,7 @@ import org.springframework.stereotype.Component; import javax.annotation.Resource; +import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -99,6 +100,24 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, return; } + Subscription subscription = new Subscription(); + subscription.setQos(coapMessage.getQosLevel().value()); + subscription.setTopicFilter(TopicUtils.normalizeTopic(coapMessage.getTopic())); + + InetSocketAddress address = coapMessage.getRemoteAddress(); + CoapSession session = sessionLoop.getSession(address); + if (session != null) { + session.refreshSubscribeTime(); + doResponseSuccess(ctx, coapMessage, session); + return; + } + + CoapSession newSession = new CoapSession(); + newSession.setAddress(address); + newSession.setToken(coapMessage.getToken()); + newSession.setSubscribeTime(System.currentTimeMillis()); + newSession.setSubscription(subscription); + newSession.setCtx(ctx); CompletableFuture future = new CompletableFuture<>(); // todo: setFuture scheduler.schedule(() -> { @@ -107,28 +126,16 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, } }, 1, TimeUnit.SECONDS); try { - Subscription subscription = new Subscription(); - subscription.setQos(coapMessage.getQosLevel().value()); - subscription.setTopicFilter(TopicUtils.normalizeTopic(coapMessage.getTopic())); - - CoapSession session = new CoapSession(); - session.setAddress(coapMessage.getRemoteAddress()); - session.setMessageId(coapMessage.getMessageId()); - session.setToken(coapMessage.getToken()); - session.setSubscribeTime(System.currentTimeMillis()); - session.setSubscription(subscription); - session.setCtx(ctx); - sessionLoop.addSession(session, future); // if the session is already exist, do not send retained message + sessionLoop.addSession(newSession, future); + doResponseSuccess(ctx, coapMessage, newSession); future.thenAccept(aVoid -> { if (!ctx.channel().isActive()) { return; } // todo: removeFuture - doResponseSuccess(ctx, coapMessage, session); - sendRetainMessage(ctx, session); - - + doResponseSuccess(ctx, coapMessage, newSession); + sendRetainMessage(ctx, newSession); }); } catch (Exception e) { logger.error("Coap Subscribe:{}", coapMessage.getRemoteAddress(), e); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index f65fed9a8..ce181b471 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -39,13 +39,16 @@ import java.util.Set; import java.util.LinkedHashSet; import java.util.Iterator; +import java.util.Random; import java.util.concurrent.ConcurrentHashMap; public class CoapSession { private static Logger logger = LoggerFactory.getLogger(CoapSession.class); + private static final int MAX_MESSAGE_ID = 65535; + private InetSocketAddress address; private ChannelHandlerContext ctx; - private int messageId; + private final int messageId = new Random().nextInt(MAX_MESSAGE_ID) + 1; private byte[] token; private int messageNum = 0; private long subscribeTime; @@ -302,10 +305,6 @@ public int getMessageId() { return messageId; } - public void setMessageId(int messageId) { - this.messageId = messageId; - } - public byte[] getToken() { return token; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index a2aea8115..cf8e527ac 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -66,9 +66,8 @@ private void doRetry() { if (retryMessageMap.isEmpty()) { return; } - long currentTime = System.currentTimeMillis(); for (RetryMessage retryMessage : retryMessageMap.values()) { - if (currentTime - retryMessage.lastSendTime < RETRY_INTERVAL) { + if (System.currentTimeMillis() - retryMessage.lastSendTime < RETRY_INTERVAL) { continue; } if (retryMessage.retryTime >= MAX_RETRY_TIME) { @@ -79,7 +78,7 @@ private void doRetry() { DatagramChannel channel = DatagramChannelManager.getInstance().getDatagramChannel(); channel.writeAndFlush(retryMessage.message); retryMessage.retryTime++; - retryMessage.lastSendTime = currentTime; + retryMessage.lastSendTime = System.currentTimeMillis(); } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 824729ef7..41148808d 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -115,7 +115,6 @@ public void addSession(CoapSession session, CompletableFuture future) { synchronized (this) { // if this session is already exist, refresh the subscription time and do nothing if (sessionMap.containsKey(address)) { - sessionMap.get(address).refreshSubscribeTime(); return; } sessionMap.put(address, session); From cde70f9499f7ab7155241da148e3e7564e7be69b Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 17:34:43 +0800 Subject: [PATCH 26/67] Add response cache, can send the same response for already received request, without further processing. But the messages sent by session-write function has not been marked. --- .../cs/channel/DatagramChannelManager.java | 29 +++-- .../protocol/coap/CoapPacketDispatcher.java | 12 ++ .../coap/handler/CoapPublishHandler.java | 8 ++ .../coap/handler/CoapSubscribeHandler.java | 109 +----------------- .../cs/protocol/rpc/RpcPacketDispatcher.java | 5 +- .../cs/session/infly/CoapResponseCache.java | 37 ++++++ .../cs/session/infly/CoapRetryManager.java | 6 +- .../rocketmq/mqtt/cs/starter/MqttServer.java | 6 +- 8 files changed, 93 insertions(+), 119 deletions(-) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index b3639ac44..d045befd0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -17,25 +17,30 @@ package org.apache.rocketmq.mqtt.cs.channel; import io.netty.channel.socket.DatagramChannel; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.springframework.stereotype.Component; +import javax.annotation.Resource; + +@Component public class DatagramChannelManager { - private static DatagramChannelManager instance; - private DatagramChannel datagramChannel; - private DatagramChannelManager() {} + @Resource + private CoapResponseCache coapResponseCache; + + private DatagramChannel channel; - public static synchronized DatagramChannelManager getInstance() { - if (instance == null) { - instance = new DatagramChannelManager(); - } - return instance; + public void setChannel(DatagramChannel channel) { + this.channel = channel; } - public void setDatagramChannel(DatagramChannel channel) { - this.datagramChannel = channel; + public DatagramChannel getChannel() { + return channel; } - public DatagramChannel getDatagramChannel() { - return datagramChannel; + public void write(CoapMessage message) { + channel.writeAndFlush(message); + coapResponseCache.put(message); } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java index 9e8a2536e..b1634a575 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java @@ -19,6 +19,7 @@ import io.netty.channel.ChannelException; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapPublishHandler; @@ -27,6 +28,7 @@ import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapHeartbeatHandler; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapAckHandler; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapPublishProcessor; import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapSubscribeProcessor; import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapConnectProcessor; @@ -81,9 +83,19 @@ public class CoapPacketDispatcher extends SimpleChannelInboundHandler { + @Resource + private CoapResponseCache coapResponseCache; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { // todo: check token if connection mode @@ -56,6 +62,7 @@ public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMes coapMessage.getRemoteAddress() ); ctx.writeAndFlush(response); + coapResponseCache.put(response); } public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { @@ -70,5 +77,6 @@ public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coap coapMessage.getRemoteAddress() ); ctx.writeAndFlush(response); + coapResponseCache.put(response); } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index f5c1aba97..35a71c221 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -33,7 +33,7 @@ import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; -import org.apache.rocketmq.mqtt.cs.session.infly.PushAction; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -58,37 +58,13 @@ public class CoapSubscribeHandler implements CoapPacketHandler responseCache = Caffeine.newBuilder().maximumSize(MAX_SIZE).build(); + + public void put(CoapMessage coapMessage) { + responseCache.put(coapMessage.getMessageId(), coapMessage); + } + + public CoapMessage get(int messageId) { + return responseCache.getIfPresent(messageId); + } + +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index cf8e527ac..32499f2ea 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -26,6 +26,7 @@ import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +import javax.annotation.Resource; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledThreadPoolExecutor; @@ -34,6 +35,9 @@ @Component public class CoapRetryManager { + @Resource + private DatagramChannelManager datagramChannelManager; + private static Logger logger = LoggerFactory.getLogger(CoapRetryManager.class); private ScheduledThreadPoolExecutor scheduler; @@ -75,7 +79,7 @@ private void doRetry() { logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); continue; } - DatagramChannel channel = DatagramChannelManager.getInstance().getDatagramChannel(); + DatagramChannel channel = datagramChannelManager.getChannel(); channel.writeAndFlush(retryMessage.message); retryMessage.retryTime++; retryMessage.lastSendTime = System.currentTimeMillis(); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java index 50d20edde..4154e8334 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/starter/MqttServer.java @@ -109,6 +109,10 @@ public class MqttServer { @Resource private ChannelManager channelManager; + + @Resource + private DatagramChannelManager datagramChannelManager; + private NioEventLoopGroup acceptorEventLoopGroup; private NioEventLoopGroup workerEventLoopGroup; @@ -319,7 +323,7 @@ private void startCoap() { .handler(new ChannelInitializer() { @Override protected void initChannel(DatagramChannel ch) throws Exception { - DatagramChannelManager.getInstance().setDatagramChannel(ch); + datagramChannelManager.setChannel(ch); ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("coap-handler", coapRPCHandler); pipeline.addLast("coap-encoder", new CoapEncoder()); From 63356933c7b1b0c061e2ccf8ce5ad4a5afc6b74d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 2 Aug 2024 18:18:02 +0800 Subject: [PATCH 27/67] move send message action to channelmanager, but bug exist --- .../cs/channel/DatagramChannelManager.java | 16 +++++ .../coap/handler/CoapSubscribeHandler.java | 21 ++++++- .../rocketmq/mqtt/cs/session/CoapSession.java | 63 ++----------------- .../mqtt/cs/session/infly/PushAction.java | 39 ++++++++++-- .../cs/session/loop/CoapSessionLoopImpl.java | 22 ++++++- 5 files changed, 95 insertions(+), 66 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index d045befd0..60386ac3b 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -18,7 +18,9 @@ import io.netty.channel.socket.DatagramChannel; import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -29,6 +31,9 @@ public class DatagramChannelManager { @Resource private CoapResponseCache coapResponseCache; + @Resource + private CoapRetryManager coapRetryManager; + private DatagramChannel channel; public void setChannel(DatagramChannel channel) { @@ -41,6 +46,17 @@ public DatagramChannel getChannel() { public void write(CoapMessage message) { channel.writeAndFlush(message); + } + + public void writeResponse(CoapMessage message) { + channel.writeAndFlush(message); coapResponseCache.put(message); } + + public void pushMessage(CoapMessage message) { + channel.writeAndFlush(message); + if (message.getType() == CoapMessageType.CON) { + coapRetryManager.addRetryMessage(message); + } + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 35a71c221..2bb2f0256 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -34,6 +34,7 @@ import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -60,6 +61,9 @@ public class CoapSubscribeHandler implements CoapPacketHandler future = new CompletableFuture<>(); // todo: setFuture scheduler.schedule(() -> { @@ -125,7 +128,21 @@ private void sendRetainMessage(ChannelHandlerContext ctx, CoapSession session) { if (message == null) { return; } - session.write(message.getPayload()); + session.messageNumIncrement(); + CoapMessage sendMessage = new CoapMessage( + Constants.COAP_VERSION, + session.getSubscription().getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, + session.getToken().length, + CoapMessageCode.CONTENT, + session.getMessageId() + session.getMessageNum(), + session.getToken(), + message.getPayload(), + session.getAddress() + ); + ctx.writeAndFlush(sendMessage); + if (session.getSubscription().getQos() > 0) { + coapRetryManager.addRetryMessage(sendMessage); + } })); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index ce181b471..083a3ce45 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -17,22 +17,14 @@ package org.apache.rocketmq.mqtt.cs.session; -import io.netty.channel.ChannelHandlerContext; -import org.apache.rocketmq.mqtt.common.model.Constants; import org.apache.rocketmq.mqtt.common.model.Message; import org.apache.rocketmq.mqtt.common.model.Subscription; -import org.apache.rocketmq.mqtt.common.model.CoapMessage; -import org.apache.rocketmq.mqtt.common.model.CoapMessageType; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; -import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.QueueOffset; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; -import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.List; import java.util.ArrayList; @@ -44,10 +36,10 @@ public class CoapSession { private static Logger logger = LoggerFactory.getLogger(CoapSession.class); + private static final int MAX_MESSAGE_ID = 65535; private InetSocketAddress address; - private ChannelHandlerContext ctx; private final int messageId = new Random().nextInt(MAX_MESSAGE_ID) + 1; private byte[] token; private int messageNum = 0; @@ -230,15 +222,15 @@ public Message nextSendMessageByOrder(Queue queue) { } } - public CoapMessage sendNewMessage(Queue queue, Message messageSend) { - CoapMessage data = write(messageSend.getPayload()); + public void sendNewMessage(Queue queue, Message messageSend) { + this.messageNum++; LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { - return null; + return; } synchronized (this) { if (messages.isEmpty()) { - return null; + return; } Iterator iterator = messages.iterator(); while (iterator.hasNext()) { @@ -252,45 +244,10 @@ public CoapMessage sendNewMessage(Queue queue, Message messageSend) { } } } - return data; - } - - public void sendRemoveSessionMessage() { - byte[] payload = "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8); - write(payload, CoapMessageCode.FORBIDDEN); - } - - public CoapMessage write(byte[] payload) { - return write(payload, CoapMessageCode.CONTENT); } - public CoapMessage write(byte[] payload, CoapMessageCode code) { + public void messageNumIncrement() { this.messageNum++; - CoapMessage data = new CoapMessage( - Constants.COAP_VERSION, - this.subscription.getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, - this.token.length, - code, - this.messageId + this.messageNum, - this.token, - payload, - this.address - ); - data.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(this.messageNum))); - if (this.ctx.channel().isActive()) { - this.ctx.writeAndFlush(data); - } else { - System.out.println("Channel is not active"); - } - return data; - } - - private byte[] intToByteArray(int value) { - byte[] byteArray = new byte[3]; - byteArray[0] = (byte) (value >> 16); - byteArray[1] = (byte) (value >> 8); - byteArray[2] = (byte) (value); - return byteArray; } public InetSocketAddress getAddress() { @@ -345,14 +302,6 @@ public void setPullSize(int pullSize) { this.pullSize = pullSize; } - public ChannelHandlerContext getCtx() { - return ctx; - } - - public void setCtx(ChannelHandlerContext ctx) { - this.ctx = ctx; - } - public Map getOffsetMap() { return offsetMap; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 1f4d472bc..f99a2caf9 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -28,12 +28,18 @@ import org.apache.rocketmq.common.MixAll; import org.apache.rocketmq.mqtt.common.facade.LmqQueueStore; import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; +import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; import org.apache.rocketmq.mqtt.common.model.Message; import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.Subscription; import org.apache.rocketmq.mqtt.common.util.MessageUtil; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.channel.ChannelInfo; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.mqtt.facotry.MqttMessageFactory; import org.apache.rocketmq.mqtt.cs.session.Session; @@ -75,7 +81,7 @@ public class PushAction { private LmqQueueStore lmqQueueStore; @Resource - private CoapRetryManager coapRetryManager; + private DatagramChannelManager datagramChannelManager; public void messageArrive(Session session, Subscription subscription, Queue queue) { if (session == null) { @@ -167,11 +173,9 @@ public void coapPush(Message message, CoapSession session, Queue queue) { message.setPayload("".getBytes()); } - Subscription subscription = session.getSubscription(); - CoapMessage sendMessage = session.sendNewMessage(queue, message); - if (subscription.getQos() > 0) { - coapRetryManager.addRetryMessage(sendMessage); - } + session.sendNewMessage(queue, message); + CoapMessage sendMessage = buildCoapMessage(message, session); + datagramChannelManager.pushMessage(sendMessage); } public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { @@ -374,4 +378,27 @@ public void _rollNext(Session session, InFlyCache.PendingDown pendingDown) { } } + private CoapMessage buildCoapMessage(Message message, CoapSession session) { + CoapMessage coapMessage = new CoapMessage( + Constants.COAP_VERSION, + session.getSubscription().getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, + session.getToken().length, + CoapMessageCode.CONTENT, + session.getMessageId() + session.getMessageNum(), + session.getToken(), + message.getPayload(), + session.getAddress() + ); + coapMessage.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(session.getMessageNum()))); + return coapMessage; + } + + private byte[] intToByteArray(int value) { + byte[] byteArray = new byte[3]; + byteArray[0] = (byte) (value >> 16); + byteArray[1] = (byte) (value >> 8); + byteArray[2] = (byte) (value); + return byteArray; + } + } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 41148808d..4c45a0537 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -26,6 +26,11 @@ import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.QueueOffset; import org.apache.rocketmq.mqtt.common.model.Subscription; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.QueueFresh; @@ -38,6 +43,7 @@ import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; @@ -66,6 +72,9 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ @Resource private QueueFresh queueFresh; + @Resource + private DatagramChannelManager datagramChannelManager; + private ScheduledThreadPoolExecutor pullService; private ScheduledThreadPoolExecutor scheduler; @@ -145,7 +154,18 @@ public CoapSession removeSession(InetSocketAddress address) { } // todo: inFlyCache.cleanResource() if (session != null) { - session.sendRemoveSessionMessage(); + session.messageNumIncrement(); + CoapMessage removeMessage = new CoapMessage( + Constants.COAP_VERSION, + session.getSubscription().getQos() == 0 ? CoapMessageType.NON : CoapMessageType.CON, + session.getToken().length, + CoapMessageCode.FORBIDDEN, + session.getMessageId() + session.getMessageNum(), + session.getToken(), + "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8), + session.getAddress() + ); + datagramChannelManager.pushMessage(removeMessage); matchAction.removeSubscription(session); // todo: persistOffset(session) } From 05cac75d7b3fbf80182fb377954d37aad28e62e8 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 5 Aug 2024 10:21:08 +0800 Subject: [PATCH 28/67] bug fix, complete: move send message action to channelmanager --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 24 +++++-------------- .../coap/handler/CoapSubscribeHandler.java | 1 - 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 11b35e957..7173abfbf 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -29,7 +29,9 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapRequestType; import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import javax.annotation.Resource; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -40,6 +42,9 @@ public class CoapDecoder extends MessageToMessageDecoder { + @Resource + private CoapResponseCache coapResponseCache; + private CoapMessageType coapType; private int coapTokenLength; private CoapMessageCode coapCode; @@ -343,24 +348,7 @@ public void sendErrorResponse(ChannelHandlerContext ctx) { remoteAddress ); ctx.writeAndFlush(response); + coapResponseCache.put(response); } - public void sendTestResponse(ChannelHandlerContext ctx) { - CoapMessage response = new CoapMessage( - Constants.COAP_VERSION, - coapType == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, - coapToken == null ? 0 : coapTokenLength, - CoapMessageCode.Valid, - coapMessageId, - coapToken, - "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), - remoteAddress - ); - if (ctx.channel().isActive()) { - ctx.writeAndFlush(response); - } else { - System.out.println("Channel is not active"); - } - - } } \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 2bb2f0256..e4632ac29 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -106,7 +106,6 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, }, 1, TimeUnit.SECONDS); try { sessionLoop.addSession(newSession, future); - doResponseSuccess(ctx, coapMessage, newSession); future.thenAccept(aVoid -> { if (!ctx.channel().isActive()) { From 808471f5699c5f9be6f2c7f56eb4b260d97ed882 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 5 Aug 2024 10:37:18 +0800 Subject: [PATCH 29/67] Move all writeAndFlush to DatagramChannelManager. --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 7 +-- .../coap/handler/CoapPublishHandler.java | 57 ++++++++----------- .../coap/handler/CoapSubscribeHandler.java | 27 ++------- .../cs/session/infly/CoapRetryManager.java | 4 +- 4 files changed, 33 insertions(+), 62 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 7173abfbf..d51a187a4 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -29,7 +29,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapRequestType; import org.apache.rocketmq.mqtt.common.model.Constants; -import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import javax.annotation.Resource; import java.net.InetSocketAddress; @@ -43,7 +43,7 @@ public class CoapDecoder extends MessageToMessageDecoder { @Resource - private CoapResponseCache coapResponseCache; + private DatagramChannelManager datagramChannelManager; private CoapMessageType coapType; private int coapTokenLength; @@ -347,8 +347,7 @@ public void sendErrorResponse(ChannelHandlerContext ctx) { errorContent.getBytes(StandardCharsets.UTF_8), remoteAddress ); - ctx.writeAndFlush(response); - coapResponseCache.put(response); + datagramChannelManager.writeResponse(response); } } \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java index 70bc9be7a..1b2f591a9 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -23,6 +23,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; import org.springframework.stereotype.Component; @@ -33,7 +34,7 @@ public class CoapPublishHandler implements CoapPacketHandler { @Resource - private CoapResponseCache coapResponseCache; + private DatagramChannelManager datagramChannelManager; @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { @@ -43,40 +44,30 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + CoapMessage response; if (upstreamHookResult.isSuccess()) { - doResponseSuccess(ctx, coapMessage); + response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.CREATED, + coapMessage.getMessageId(), + coapMessage.getToken(), + null, + coapMessage.getRemoteAddress() + ); } else { - doResponseFail(ctx, coapMessage, upstreamHookResult.getRemark()); + response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId(), + coapMessage.getToken(), + upstreamHookResult.getRemark().getBytes(), + coapMessage.getRemoteAddress() + ); } - } - - public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, String errContent) { - CoapMessage response = new CoapMessage( - Constants.COAP_VERSION, - coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, - coapMessage.getTokenLength(), - CoapMessageCode.INTERNAL_SERVER_ERROR, - coapMessage.getMessageId(), - coapMessage.getToken(), - errContent.getBytes(), - coapMessage.getRemoteAddress() - ); - ctx.writeAndFlush(response); - coapResponseCache.put(response); - } - - public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { - CoapMessage response = new CoapMessage( - Constants.COAP_VERSION, - coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, - coapMessage.getTokenLength(), - CoapMessageCode.CREATED, - coapMessage.getMessageId(), - coapMessage.getToken(), - null, - coapMessage.getRemoteAddress() - ); - ctx.writeAndFlush(response); - coapResponseCache.put(response); + datagramChannelManager.writeResponse(response); } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index e4632ac29..02bdb5044 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -31,10 +31,9 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.util.TopicUtils; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; -import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; -import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -59,10 +58,7 @@ public class CoapSubscribeHandler implements CoapPacketHandler 0) { - coapRetryManager.addRetryMessage(sendMessage); - } + datagramChannelManager.pushMessage(sendMessage); })); } @@ -157,12 +150,7 @@ public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMes coapMessage.getRemoteAddress() ); response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(1))); - if (ctx.channel().isActive()) { - ctx.writeAndFlush(response); - coapResponseCache.put(response); - } else { - System.out.println("Channel is not active"); - } + datagramChannelManager.writeResponse(response); } @@ -187,12 +175,7 @@ public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coap coapMessage.getRemoteAddress() ); response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(session.getMessageNum()))); - if (ctx.channel().isActive()) { - ctx.writeAndFlush(response); - coapResponseCache.put(response); - } else { - System.out.println("Channel is not active"); - } + datagramChannelManager.writeResponse(response); } } \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index 32499f2ea..c2a683205 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -17,7 +17,6 @@ package org.apache.rocketmq.mqtt.cs.session.infly; -import io.netty.channel.socket.DatagramChannel; import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; @@ -79,8 +78,7 @@ private void doRetry() { logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); continue; } - DatagramChannel channel = datagramChannelManager.getChannel(); - channel.writeAndFlush(retryMessage.message); + datagramChannelManager.write(retryMessage.message); retryMessage.retryTime++; retryMessage.lastSendTime = System.currentTimeMillis(); } From 358a37945780862cf6d86e6be41244ac8f62263f Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 5 Aug 2024 11:09:39 +0800 Subject: [PATCH 30/67] code formatting. --- .../java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 083a3ce45..14b700aa0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -50,6 +50,8 @@ public class CoapSession { private Map offsetMap = new ConcurrentHashMap<>(16); Map> sendingMessages = new ConcurrentHashMap<>(16); + int timeForTest = 0; + public CoapSession() {} public void refreshSubscribeTime() { @@ -223,7 +225,7 @@ public Message nextSendMessageByOrder(Queue queue) { } public void sendNewMessage(Queue queue, Message messageSend) { - this.messageNum++; + messageNumIncrement(); LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { return; From 525ca0c0dd0ee62b11dc50128be748907404bc09 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 5 Aug 2024 15:47:20 +0800 Subject: [PATCH 31/67] code formatting. --- .../java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 14b700aa0..8d14d687a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -50,8 +50,6 @@ public class CoapSession { private Map offsetMap = new ConcurrentHashMap<>(16); Map> sendingMessages = new ConcurrentHashMap<>(16); - int timeForTest = 0; - public CoapSession() {} public void refreshSubscribeTime() { From a7a7cab282295ac00116fccbcdd0986240c3b361 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 5 Aug 2024 19:38:50 +0800 Subject: [PATCH 32/67] Complete: update to latest messageID in retry manager, to keep increasing message number. --- .../mqtt/common/model/CoapMessage.java | 17 +++++++++++++ .../cs/channel/DatagramChannelManager.java | 5 ++-- .../coap/handler/CoapSubscribeHandler.java | 18 +++----------- .../rocketmq/mqtt/cs/session/CoapSession.java | 16 ++++++++----- .../cs/session/infly/CoapRetryManager.java | 24 +++++++++++++++++-- .../mqtt/cs/session/infly/PushAction.java | 14 ++--------- .../cs/session/loop/CoapSessionLoopImpl.java | 2 +- 7 files changed, 58 insertions(+), 38 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java index 054830726..d396ef90f 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java @@ -105,6 +105,10 @@ public List getOptions() { return options; } + public void clearOptions() { + this.options.clear(); + } + public void setOptions(List options) { this.options = options; } @@ -113,6 +117,10 @@ public void addOption(CoapMessageOption option) { this.options.add(option); } + public void addObserveOption(int value) { + this.options.add(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(value))); + } + public byte[] getPayload() { return payload; } @@ -129,6 +137,15 @@ public void setRemoteAddress(InetSocketAddress remoteAddress) { this.remoteAddress = remoteAddress; } + private byte[] intToByteArray(int value) { + byte[] byteArray = new byte[3]; + byteArray[0] = (byte) (value >> 16); + byteArray[1] = (byte) (value >> 8); + byteArray[2] = (byte) (value); + return byteArray; + } + + @Override public String toString() { return "CoapMessage{" + diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index 60386ac3b..0de78aceb 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -19,6 +19,7 @@ import io.netty.channel.socket.DatagramChannel; import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.springframework.stereotype.Component; @@ -53,10 +54,10 @@ public void writeResponse(CoapMessage message) { coapResponseCache.put(message); } - public void pushMessage(CoapMessage message) { + public void pushMessage(CoapSession session, CoapMessage message) { channel.writeAndFlush(message); if (message.getType() == CoapMessageType.CON) { - coapRetryManager.addRetryMessage(message); + coapRetryManager.addRetryMessage(session, message); } } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 02bdb5044..ff0e19135 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -27,8 +27,6 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; @@ -134,7 +132,7 @@ private void sendRetainMessage(ChannelHandlerContext ctx, CoapSession session) { message.getPayload(), session.getAddress() ); - datagramChannelManager.pushMessage(sendMessage); + datagramChannelManager.pushMessage(session, sendMessage); })); } @@ -149,20 +147,10 @@ public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMes errContent.getBytes(StandardCharsets.UTF_8), coapMessage.getRemoteAddress() ); - response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(1))); + response.addObserveOption(1); datagramChannelManager.writeResponse(response); } - - // change an integer into a byte array with length 3 - private byte[] intToByteArray(int value) { - byte[] byteArray = new byte[3]; - byteArray[0] = (byte) (value >> 16); - byteArray[1] = (byte) (value >> 8); - byteArray[2] = (byte) (value); - return byteArray; - } - public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, CoapSession session) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, @@ -174,7 +162,7 @@ public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coap "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), coapMessage.getRemoteAddress() ); - response.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(session.getMessageNum()))); + response.addObserveOption(session.getMessageNum()); datagramChannelManager.writeResponse(response); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 8d14d687a..9d279ff5a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -64,11 +64,11 @@ public QueueOffset getQueueOffset(Queue queue) { } public void freshQueue(Set queues) { - if (this.subscription == null) { + if (subscription == null) { throw new RuntimeException("subscription is null"); } if (queues == null) { - logger.warn("queues is null when freshQueue,{},{}", this.address, this.subscription); + logger.warn("queues is null when freshQueue,{},{}", address, subscription); return; } @@ -94,7 +94,7 @@ public void freshQueue(Set queues) { } if (queues.isEmpty()) { - logger.warn("queues is empty when freshQueue,{},{}", this.address, this.subscription); + logger.warn("queues is empty when freshQueue,{},{}", address, subscription); } } @@ -104,7 +104,7 @@ public void addOffset(Queue queue, QueueOffset offset) { public void updateQueueOffset(Queue queue, Message message) { if (!offsetMap.containsKey(queue)) { - logger.warn("failed update queue offset,not found queueOffset,{},{},{}", this.address, this.subscription, + logger.warn("failed update queue offset,not found queueOffset,{},{},{}", address, subscription, queue); return; } @@ -126,7 +126,7 @@ public boolean addSendingMessages(Queue queue, List messages) { sendingMessages.putIfAbsent(queue, new LinkedHashSet<>(8)); } if (!offsetMap.containsKey(queue)) { - logger.warn("not found queueOffset,{},{},{}", this.address, this.subscription, queue); + logger.warn("not found queueOffset,{},{},{}", address, subscription, queue); return false; } boolean add =false; @@ -246,8 +246,12 @@ public void sendNewMessage(Queue queue, Message messageSend) { } } + public int getNextMessageID() { + return messageId + messageNum; + } + public void messageNumIncrement() { - this.messageNum++; + messageNum++; } public InetSocketAddress getAddress() { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index c2a683205..e8815380e 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -20,6 +20,7 @@ import org.apache.rocketmq.common.ThreadFactoryImpl; import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -53,8 +54,8 @@ public void init() { scheduler.scheduleWithFixedDelay(() -> doRetry(), SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); } - public void addRetryMessage(CoapMessage message) { - retryMessageMap.put(message.getMessageId(), new RetryMessage(message.getMessageId(), message)); + public void addRetryMessage(CoapSession session, CoapMessage message) { + retryMessageMap.put(message.getMessageId(), new RetryMessage(message.getMessageId(), message, session)); } public void removeRetryMessage(int messageId) { @@ -78,6 +79,17 @@ private void doRetry() { logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); continue; } + // update messageID if session has newer messageID + CoapSession session = retryMessage.session; + int latestMessageNum = session.getMessageNum(); + int latestMessageID = session.getMessageId() + latestMessageNum; + if (latestMessageID > retryMessage.messageId) { + retryMessage.messageId = latestMessageID; + retryMessage.message.setMessageId(latestMessageID); + retryMessage.message.clearOptions(); + retryMessage.message.addObserveOption(latestMessageNum); + session.messageNumIncrement(); + } datagramChannelManager.write(retryMessage.message); retryMessage.retryTime++; retryMessage.lastSendTime = System.currentTimeMillis(); @@ -87,6 +99,7 @@ private void doRetry() { public class RetryMessage { private int messageId; private CoapMessage message; + private CoapSession session; private int retryTime = 0; private long lastSendTime = System.currentTimeMillis(); @@ -94,5 +107,12 @@ public RetryMessage(int messageId, CoapMessage message) { this.messageId = messageId; this.message = message; } + + public RetryMessage(int messageId, CoapMessage message, CoapSession session) { + this.messageId = messageId; + this.message = message; + this.session = session; + } + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index f99a2caf9..2b3a3fda4 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -31,8 +31,6 @@ import org.apache.rocketmq.mqtt.common.model.Constants; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOption; -import org.apache.rocketmq.mqtt.common.model.CoapMessageOptionNumber; import org.apache.rocketmq.mqtt.common.model.Message; import org.apache.rocketmq.mqtt.common.model.Queue; import org.apache.rocketmq.mqtt.common.model.Subscription; @@ -175,7 +173,7 @@ public void coapPush(Message message, CoapSession session, Queue queue) { session.sendNewMessage(queue, message); CoapMessage sendMessage = buildCoapMessage(message, session); - datagramChannelManager.pushMessage(sendMessage); + datagramChannelManager.pushMessage(session, sendMessage); } public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { @@ -389,16 +387,8 @@ private CoapMessage buildCoapMessage(Message message, CoapSession session) { message.getPayload(), session.getAddress() ); - coapMessage.addOption(new CoapMessageOption(CoapMessageOptionNumber.OBSERVE, intToByteArray(session.getMessageNum()))); + coapMessage.addObserveOption(session.getMessageNum()); return coapMessage; } - private byte[] intToByteArray(int value) { - byte[] byteArray = new byte[3]; - byteArray[0] = (byte) (value >> 16); - byteArray[1] = (byte) (value >> 8); - byteArray[2] = (byte) (value); - return byteArray; - } - } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 4c45a0537..40b239008 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -165,7 +165,7 @@ public CoapSession removeSession(InetSocketAddress address) { "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8), session.getAddress() ); - datagramChannelManager.pushMessage(removeMessage); + datagramChannelManager.pushMessage(session, removeMessage); matchAction.removeSubscription(session); // todo: persistOffset(session) } From 01fa0c7a9b125a158edfa66b615db4dccdcf764e Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 14:41:29 +0800 Subject: [PATCH 33/67] Complete: refresh subscription each time receiving an ACK --- .../rocketmq/mqtt/cs/session/infly/CoapRetryManager.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index e8815380e..f60ca21be 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -59,7 +59,11 @@ public void addRetryMessage(CoapSession session, CoapMessage message) { } public void removeRetryMessage(int messageId) { - retryMessageMap.remove(messageId); + RetryMessage removedMessage = retryMessageMap.remove(messageId); + // refresh subscription each time receiving an ACK + if (removedMessage.session != null) { + removedMessage.session.refreshSubscribeTime(); + } } public boolean contains(int messageId) { From 972acaad741f12c37f563dfefc235b27fdbf1dde Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 14:48:52 +0800 Subject: [PATCH 34/67] Complete: refresh subscription each time receiving an ACK --- .../cs/protocol/coap/handler/CoapAckHandler.java | 2 +- .../mqtt/cs/session/infly/CoapRetryManager.java | 14 +++++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java index cb43a569d..6e5956033 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -40,7 +40,7 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { int messageId = coapMessage.getMessageId(); if (coapRetryManager.contains(messageId)) { - coapRetryManager.removeRetryMessage(messageId); + coapRetryManager.ackMessage(messageId); } } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index f60ca21be..ef2e9beef 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -58,7 +58,15 @@ public void addRetryMessage(CoapSession session, CoapMessage message) { retryMessageMap.put(message.getMessageId(), new RetryMessage(message.getMessageId(), message, session)); } - public void removeRetryMessage(int messageId) { + public RetryMessage removeRetryMessage(int messageId) { + return retryMessageMap.remove(messageId); + } + + public boolean contains(int messageId) { + return retryMessageMap.containsKey(messageId); + } + + public void ackMessage(int messageId) { RetryMessage removedMessage = retryMessageMap.remove(messageId); // refresh subscription each time receiving an ACK if (removedMessage.session != null) { @@ -66,10 +74,6 @@ public void removeRetryMessage(int messageId) { } } - public boolean contains(int messageId) { - return retryMessageMap.containsKey(messageId); - } - private void doRetry() { if (retryMessageMap.isEmpty()) { return; From 9f219bcc9ee9b6c7d8eeaf707d3ae12f51319975 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 14:57:34 +0800 Subject: [PATCH 35/67] Complete: remove session if exceed max retry time. --- .../cs/channel/DatagramChannelManager.java | 1 + .../cs/session/infly/CoapRetryManager.java | 18 +++++++++++++++++- .../cs/session/loop/CoapSessionLoopImpl.java | 2 +- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index 0de78aceb..c5aa1e492 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -60,4 +60,5 @@ public void pushMessage(CoapSession session, CoapMessage message) { coapRetryManager.addRetryMessage(session, message); } } + } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index ef2e9beef..a57a5ca5b 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -21,6 +21,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessage; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -38,6 +39,9 @@ public class CoapRetryManager { @Resource private DatagramChannelManager datagramChannelManager; + @Resource + private CoapSessionLoop coapSessionLoop; + private static Logger logger = LoggerFactory.getLogger(CoapRetryManager.class); private ScheduledThreadPoolExecutor scheduler; @@ -83,7 +87,19 @@ private void doRetry() { continue; } if (retryMessage.retryTime >= MAX_RETRY_TIME) { - removeRetryMessage(retryMessage.messageId); + RetryMessage removedMessage = removeRetryMessage(retryMessage.messageId); + CoapSession session = removedMessage.session; + // remove session if exceed max retry time + if (session != null) { + // release session from all relative retry message + for (RetryMessage message : retryMessageMap.values()) { + if (message.session == session) { + message.session = null; + } + } + // remove from session loop + coapSessionLoop.removeSession(session.getAddress()); + } logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); continue; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 40b239008..4b62f17f6 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -165,7 +165,7 @@ public CoapSession removeSession(InetSocketAddress address) { "Subscription is expired, please subscribe again.".getBytes(StandardCharsets.UTF_8), session.getAddress() ); - datagramChannelManager.pushMessage(session, removeMessage); + datagramChannelManager.pushMessage(null, removeMessage); matchAction.removeSubscription(session); // todo: persistOffset(session) } From 09170a8e5115bc78558c0a00247ef0288973e9ad Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 14:58:30 +0800 Subject: [PATCH 36/67] remove unused method --- .../rocketmq/mqtt/cs/session/infly/CoapRetryManager.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index a57a5ca5b..82f5fc592 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -127,11 +127,6 @@ public class RetryMessage { private int retryTime = 0; private long lastSendTime = System.currentTimeMillis(); - public RetryMessage(int messageId, CoapMessage message) { - this.messageId = messageId; - this.message = message; - } - public RetryMessage(int messageId, CoapMessage message, CoapSession session) { this.messageId = messageId; this.message = message; From f40a1343a4f7aa4387fa5562f9511403a4712ff6 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 15:24:44 +0800 Subject: [PATCH 37/67] fix bug: do not update messageID in retry manager if session is null. --- .../cs/channel/DatagramChannelManager.java | 1 - .../cs/session/infly/CoapRetryManager.java | 18 ++++++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index c5aa1e492..0de78aceb 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -60,5 +60,4 @@ public void pushMessage(CoapSession session, CoapMessage message) { coapRetryManager.addRetryMessage(session, message); } } - } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index 82f5fc592..1ea1c5a60 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -105,14 +105,16 @@ private void doRetry() { } // update messageID if session has newer messageID CoapSession session = retryMessage.session; - int latestMessageNum = session.getMessageNum(); - int latestMessageID = session.getMessageId() + latestMessageNum; - if (latestMessageID > retryMessage.messageId) { - retryMessage.messageId = latestMessageID; - retryMessage.message.setMessageId(latestMessageID); - retryMessage.message.clearOptions(); - retryMessage.message.addObserveOption(latestMessageNum); - session.messageNumIncrement(); + if (session != null) { + int latestMessageNum = session.getMessageNum(); + int latestMessageID = session.getMessageId() + latestMessageNum; + if (latestMessageID > retryMessage.messageId) { + retryMessage.messageId = latestMessageID; + retryMessage.message.setMessageId(latestMessageID); + retryMessage.message.clearOptions(); + retryMessage.message.addObserveOption(latestMessageNum); + session.messageNumIncrement(); + } } datagramChannelManager.write(retryMessage.message); retryMessage.retryTime++; From b059f4b504094da0b94181e041343aee7700fd06 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 17:00:23 +0800 Subject: [PATCH 38/67] Add comments and code formatting for CoapDecoder. --- .../mqtt/cs/protocol/coap/CoapDecoder.java | 139 +++++++----------- 1 file changed, 55 insertions(+), 84 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index d51a187a4..5d61213e1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -39,7 +39,6 @@ import java.util.stream.Collectors; - public class CoapDecoder extends MessageToMessageDecoder { @Resource @@ -59,7 +58,7 @@ public class CoapDecoder extends MessageToMessageDecoder { @Override public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List out) { - + // Get data and remote address ByteBuf in = packet.content(); remoteAddress = packet.sender(); @@ -67,9 +66,8 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List> 4) & 0x03); @@ -89,133 +86,118 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List Constants.COAP_MAX_TOKEN_LENGTH) { errorCode = CoapMessageCode.BAD_REQUEST; errorContent = "Format-Error: The length of token is too long!"; - sendErrorResponse(ctx); - // Skip unread bytes - in.skipBytes(in.readableBytes()); + sendErrorResponse(); + in.skipBytes(in.readableBytes()); // Skip unread bytes return; } - - // Handle code try { coapCode = CoapMessageCode.valueOf(in.readUnsignedByte()); } catch (IllegalArgumentException e) { errorCode = CoapMessageCode.BAD_REQUEST; errorContent = "Format-Error: The code is not defined!"; - sendErrorResponse(ctx); - // Skip unread bytes - in.skipBytes(in.readableBytes()); + sendErrorResponse(); + in.skipBytes(in.readableBytes()); // Skip unread bytes return; } if (!CoapMessageCode.isRequestCode(coapCode) && !CoapMessageCode.isEmptyCode(coapCode)) { errorCode = CoapMessageCode.BAD_REQUEST; errorContent = "Format-Error: The code must be a request code!"; - sendErrorResponse(ctx); - // Skip unread bytes - in.skipBytes(in.readableBytes()); + sendErrorResponse(); + in.skipBytes(in.readableBytes()); // Skip unread bytes return; } - // Handle messageID + // Handle messageID. coapMessageId = in.readUnsignedShort(); - // Handle token + // Handle token. if (in.readableBytes() < coapTokenLength) { - // Return 4.00 Response - errorCode = CoapMessageCode.BAD_REQUEST; + errorCode = CoapMessageCode.BAD_REQUEST; // Return 4.00 Response errorContent = "Format-Error: The length of remaining readable bytes is less than tokenLength!"; - sendErrorResponse(ctx); - // Skip unread bytes - in.skipBytes(in.readableBytes()); + sendErrorResponse(); + in.skipBytes(in.readableBytes()); // Skip unread bytes return; } coapToken = new byte[coapTokenLength]; in.readBytes(coapToken); + // Construct request message. CoapRequestMessage coapMessage = new CoapRequestMessage(version, coapType, coapTokenLength, coapCode, coapMessageId, coapToken, remoteAddress); - // Handle ACK + // Handle ACK, which is an empty message. if (coapType == CoapMessageType.ACK) { coapMessage.setRequestType(CoapRequestType.ACK); ctx.fireChannelRead(coapMessage); return; } - // Handle options + // Handle options. There may be multiple options, each containing option delta, option length and option value. The end of options is marked by payload marker. int nextByte; int optionNumber = 0; List uriPaths = new ArrayList<>(); while (in.readableBytes() > 0) { - nextByte = in.readUnsignedByte(); + // Terminate when meeting payload marker. if (nextByte == Constants.COAP_PAYLOAD_MARKER) { break; } - + // Handle optionDelta and optionLength. int optionDelta = nextByte >> 4; int optionLength = nextByte & 0x0F; - + // Add up extended delta if delta is larger than 12. if (optionDelta == 13) { optionDelta += in.readUnsignedByte(); } else if (optionDelta == 14) { optionDelta += 255 + in.readUnsignedShort(); } else if (optionDelta == 15) { - // Return 4.00 Response - errorCode = CoapMessageCode.BAD_REQUEST; + errorCode = CoapMessageCode.BAD_REQUEST; // Return 4.00 Response errorContent = "Format-Error: OptionDelta can not be 15!"; - sendErrorResponse(ctx); + sendErrorResponse(); in.skipBytes(in.readableBytes()); return; } - optionNumber += optionDelta; // current optionNumber = last optionNumber + optionDelta - if (!CoapMessageOptionNumber.isValid(optionNumber)) { - // Return 4.02 Response - errorCode = CoapMessageCode.BAD_OPTION; + errorCode = CoapMessageCode.BAD_OPTION; // Return 4.02 Response errorContent = "Format-Error: Option number is not defined!"; - sendErrorResponse(ctx); + sendErrorResponse(); in.skipBytes(in.readableBytes()); return; } - + // Add up extended length if length is larger than 12. if (optionLength == 13) { optionLength += in.readUnsignedByte(); } else if (optionLength == 14) { optionLength += 255 + in.readUnsignedShort(); } else if (optionLength == 15) { - // Return 4.00 Response - errorCode = CoapMessageCode.BAD_REQUEST; + errorCode = CoapMessageCode.BAD_REQUEST; // Return 4.00 Response errorContent = "Format-Error: OptionLength can not be 15!"; - sendErrorResponse(ctx); + sendErrorResponse(); in.skipBytes(in.readableBytes()); return; } - if (in.readableBytes() < optionLength) { - // Return 4.00 Response - errorCode = CoapMessageCode.BAD_REQUEST; + errorCode = CoapMessageCode.BAD_REQUEST; // Return 4.00 Response errorContent = "Format-Error: The number of readable bytes is less than optionLength"; - sendErrorResponse(ctx); + sendErrorResponse(); in.skipBytes(in.readableBytes()); return; } + + // Handle option value. Need further process if optionNumber is URI_PATH, URI_QUERY or OBSERVE. byte[] optionValue = new byte[optionLength]; in.readBytes(optionValue); - if (optionNumber == CoapMessageOptionNumber.URI_PATH.value()) { uriPaths.add(new String(optionValue, StandardCharsets.UTF_8)); } - if (optionNumber == CoapMessageOptionNumber.URI_QUERY.value()) { String query = new String(optionValue, StandardCharsets.UTF_8); String[] parts = query.split(Constants.COAP_QUERY_DELIMITER, 2); if (parts.length != 2) { - // Return 4.00 Response - errorCode = CoapMessageCode.BAD_REQUEST; + errorCode = CoapMessageCode.BAD_REQUEST; // Return 4.00 Response errorContent = "Format-Error: The Format of Observe is not correct!"; - sendErrorResponse(ctx); - // Skip unread bytes - in.skipBytes(in.readableBytes()); + sendErrorResponse(); + in.skipBytes(in.readableBytes()); // Skip unread bytes return; } switch (parts[0]) { @@ -239,35 +221,30 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List Date: Tue, 6 Aug 2024 17:05:49 +0800 Subject: [PATCH 39/67] Add comment to CoapEncoder. --- .../rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java index eefe27458..f182616b4 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapEncoder.java @@ -30,19 +30,19 @@ public class CoapEncoder extends MessageToMessageEncoder { @Override public void encode(ChannelHandlerContext ctx, CoapMessage msg, List out) throws Exception { - + // Get buffer to write bytes to send. ByteBuf buffer = Unpooled.buffer(); - // Handle Version | Type | TokenLength + // Handle Version | Type | TokenLength into first byte. byte firstByte = (byte)((msg.getVersion() << 6) | (msg.getType().value() << 4) | (msg.getTokenLength() & 0x0F)); buffer.writeByte(firstByte); - // Handle Code, MessageID, Token + // Handle Code, MessageID, Token. buffer.writeByte(msg.getCode().value()); buffer.writeShort(msg.getMessageId()); buffer.writeBytes(msg.getToken()); - // Handle Options + // Handle Options. if (!msg.getOptions().isEmpty()) { int prevOptionNumber = 0; for (CoapMessageOption option : msg.getOptions()) { @@ -72,13 +72,13 @@ public void encode(ChannelHandlerContext ctx, CoapMessage msg, List out) } } - // Handle Payload if not empty + // Handle Payload if not empty. if (msg.getPayload() != null && msg.getPayload().length > 0) { buffer.writeByte((byte)0xFF); buffer.writeBytes(msg.getPayload()); } - // Send Response + // Send Response. DatagramPacket responsePacket = new DatagramPacket(buffer, msg.getRemoteAddress()); out.add(responsePacket); From 144befb397aa051e6489a831c0e136a338900525 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 17:14:01 +0800 Subject: [PATCH 40/67] Add comments and code formatting. --- .../mqtt/cs/protocol/coap/CoapPacketDispatcher.java | 10 +++++----- .../rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java | 9 ++++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java index b1634a575..abff88a0c 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapPacketDispatcher.java @@ -45,8 +45,8 @@ @Component public class CoapPacketDispatcher extends SimpleChannelInboundHandler { - private static Logger logger = LoggerFactory.getLogger(CoapPacketDispatcher.class); + @Resource private CoapPublishHandler coapPublishHandler; @@ -87,15 +87,15 @@ public class CoapPacketDispatcher extends SimpleChannelInboundHandlerprocess=>postHandler boolean preResult = preHandler(ctx, msg); if (!preResult) { return; @@ -194,4 +194,4 @@ public CompletableFuture processCoapMessage(CoapRequestMessage msg) return hookResult; } -} +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java index cd26ce24a..1ae26c94e 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.cs.protocol.coap; import io.netty.channel.ChannelHandlerContext; @@ -45,20 +44,20 @@ public class CoapRPCHandler extends SimpleChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception { - // get sender socket address + // Get sender socket address. InetSocketAddress address = packet.sender(); - // get machines + // Get all machines. Set connectorNodes = metaPersistManager.getConnectNodeSet(); if (connectorNodes == null || connectorNodes.isEmpty()) { throw new RemotingException("No Connect Nodes"); } - // calculate machine index to forward + // Calculate machine index with ip-port to forward the packet. int hash = address.toString().hashCode(); int nodeNum = Math.abs(hash % connectorNodes.size()); List nodeList = new ArrayList<>(connectorNodes); Collections.sort(nodeList); String forwardNode = nodeList.get(nodeNum); - // forward the packet if not for localhost + // Forward the packet if not for localhost, otherwise fire to next channel. if (InetAddress.getLocalHost().getHostAddress().equals(forwardNode)) { ctx.fireChannelRead(packet); } else { From 78c2a627564576f168ef6e090623f8b96fd6536d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 17:25:28 +0800 Subject: [PATCH 41/67] Add comments and code formatting. --- .../protocol/coap/handler/CoapAckHandler.java | 1 + .../coap/handler/CoapPublishHandler.java | 2 +- .../coap/handler/CoapSubscribeHandler.java | 27 ++++++++++--------- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java index 6e5956033..098a6db37 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -38,6 +38,7 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + // Ack the relative message in retry manager. int messageId = coapMessage.getMessageId(); if (coapRetryManager.contains(messageId)) { coapRetryManager.ackMessage(messageId); diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java index 1b2f591a9..8fedb4198 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -25,7 +25,6 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; -import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -44,6 +43,7 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + // Send response to client with success/error content. CoapMessage response; if (upstreamHookResult.isSuccess()) { response = new CoapMessage( diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index ff0e19135..a7ca03f57 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.cs.protocol.coap.handler; import io.netty.channel.ChannelHandlerContext; @@ -46,7 +45,6 @@ @Component public class CoapSubscribeHandler implements CoapPacketHandler { - private static Logger logger = LoggerFactory.getLogger(CoapSubscribeHandler.class); @Resource @@ -68,24 +66,27 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - // todo: response ack + // Send error response if upstream fail. if (!upstreamHookResult.isSuccess()) { - doResponseFail(ctx, coapMessage, upstreamHookResult.getRemark()); + doResponseFail(coapMessage, upstreamHookResult.getRemark()); return; } + // Construct subscription. Subscription subscription = new Subscription(); subscription.setQos(coapMessage.getQosLevel().value()); subscription.setTopicFilter(TopicUtils.normalizeTopic(coapMessage.getTopic())); + // Get session from sessionLoop if it is already existed, otherwise create a new one. InetSocketAddress address = coapMessage.getRemoteAddress(); CoapSession session = sessionLoop.getSession(address); + // If session already exist, refresh subscribe time and send response. if (session != null) { session.refreshSubscribeTime(); - doResponseSuccess(ctx, coapMessage, session); + doResponseSuccess(coapMessage, session); return; } - + // If it is a new session, create and add to sessionLoop. And send response and retained message later. CoapSession newSession = new CoapSession(); newSession.setAddress(address); newSession.setToken(coapMessage.getToken()); @@ -100,14 +101,13 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, }, 1, TimeUnit.SECONDS); try { sessionLoop.addSession(newSession, future); - future.thenAccept(aVoid -> { if (!ctx.channel().isActive()) { return; } // todo: removeFuture - doResponseSuccess(ctx, coapMessage, newSession); - sendRetainMessage(ctx, newSession); + doResponseSuccess(coapMessage, newSession); + sendRetainMessage(newSession); }); } catch (Exception e) { logger.error("Coap Subscribe:{}", coapMessage.getRemoteAddress(), e); @@ -115,7 +115,8 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, } - private void sendRetainMessage(ChannelHandlerContext ctx, CoapSession session) { + private void sendRetainMessage(CoapSession session) { + // Get retainedMessage from persist manager and send to client. CompletableFuture retainedMessage = retainedPersistManager.getRetainedMessage(session.getSubscription().getTopicFilter()); retainedMessage.whenComplete(((message, throwable) -> { if (message == null) { @@ -136,7 +137,7 @@ private void sendRetainMessage(ChannelHandlerContext ctx, CoapSession session) { })); } - public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, String errContent) { + public void doResponseFail(CoapRequestMessage coapMessage, String errContent) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, CoapMessageType.ACK, @@ -151,7 +152,7 @@ public void doResponseFail(ChannelHandlerContext ctx, CoapRequestMessage coapMes datagramChannelManager.writeResponse(response); } - public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, CoapSession session) { + public void doResponseSuccess(CoapRequestMessage coapMessage, CoapSession session) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, @@ -159,7 +160,7 @@ public void doResponseSuccess(ChannelHandlerContext ctx, CoapRequestMessage coap CoapMessageCode.CONTENT, coapMessage.getMessageId(), coapMessage.getToken(), - "Hello, I have accept your request successfully!".getBytes(StandardCharsets.UTF_8), + null, coapMessage.getRemoteAddress() ); response.addObserveOption(session.getMessageNum()); From 81c73af250c668bb683178f6b7809c8242b4920d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 6 Aug 2024 17:54:46 +0800 Subject: [PATCH 42/67] Add comments and code formatting. --- .../cs/session/infly/CoapResponseCache.java | 1 + .../cs/session/infly/CoapRetryManager.java | 19 +++++++------- .../mqtt/cs/session/loop/CoapSessionLoop.java | 25 ++++++++++++++++++- .../cs/session/loop/CoapSessionLoopImpl.java | 11 ++++---- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java index 428f333f8..049402f15 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java @@ -24,6 +24,7 @@ @Component public class CoapResponseCache { private static final int MAX_SIZE = 10000; + private Cache responseCache = Caffeine.newBuilder().maximumSize(MAX_SIZE).build(); public void put(CoapMessage coapMessage) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index 1ea1c5a60..9bcfc6d89 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.cs.session.infly; import org.apache.rocketmq.common.ThreadFactoryImpl; @@ -35,6 +34,7 @@ @Component public class CoapRetryManager { + private static Logger logger = LoggerFactory.getLogger(CoapRetryManager.class); @Resource private DatagramChannelManager datagramChannelManager; @@ -42,8 +42,6 @@ public class CoapRetryManager { @Resource private CoapSessionLoop coapSessionLoop; - private static Logger logger = LoggerFactory.getLogger(CoapRetryManager.class); - private ScheduledThreadPoolExecutor scheduler; private ConcurrentMap retryMessageMap = new ConcurrentHashMap<>(1024); @@ -55,7 +53,7 @@ public class CoapRetryManager { @PostConstruct public void init() { scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_retry_message_thread_")); - scheduler.scheduleWithFixedDelay(() -> doRetry(), SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); + scheduler.scheduleWithFixedDelay(this::doRetry, SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); } public void addRetryMessage(CoapSession session, CoapMessage message) { @@ -72,7 +70,7 @@ public boolean contains(int messageId) { public void ackMessage(int messageId) { RetryMessage removedMessage = retryMessageMap.remove(messageId); - // refresh subscription each time receiving an ACK + // Refresh subscription each time receiving an ACK. if (removedMessage.session != null) { removedMessage.session.refreshSubscribeTime(); } @@ -89,21 +87,21 @@ private void doRetry() { if (retryMessage.retryTime >= MAX_RETRY_TIME) { RetryMessage removedMessage = removeRetryMessage(retryMessage.messageId); CoapSession session = removedMessage.session; - // remove session if exceed max retry time + // Remove session if exceed max retry time. if (session != null) { - // release session from all relative retry message + // Release session from all relative retry message. for (RetryMessage message : retryMessageMap.values()) { if (message.session == session) { message.session = null; } } - // remove from session loop + // Remove from session loop. coapSessionLoop.removeSession(session.getAddress()); } logger.info("coap retry message expired, messageId:{}", retryMessage.messageId); continue; } - // update messageID if session has newer messageID + // Update messageID if session has newer messageID. CoapSession session = retryMessage.session; if (session != null) { int latestMessageNum = session.getMessageNum(); @@ -116,6 +114,7 @@ private void doRetry() { session.messageNumIncrement(); } } + // Send retry message and refresh retry info. datagramChannelManager.write(retryMessage.message); retryMessage.retryTime++; retryMessage.lastSendTime = System.currentTimeMillis(); @@ -134,6 +133,6 @@ public RetryMessage(int messageId, CoapMessage message, CoapSession session) { this.message = message; this.session = session; } - } + } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java index 1b1ababd0..cb61945cc 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoop.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.cs.session.loop; import org.apache.rocketmq.mqtt.common.model.Queue; @@ -25,11 +24,35 @@ public interface CoapSessionLoop { + /** + * Add one coap session. + * + * @param session + * @param future + */ void addSession(CoapSession session, CompletableFuture future); + /** + * Get one coap session by ip-port. + * + * @param address + * @return + */ CoapSession getSession(InetSocketAddress address); + /** + * Remove one coap session. + * + * @param address + * @return + */ CoapSession removeSession(InetSocketAddress address); + /** + * notify to pull message from queue + * + * @param session + * @param queue + */ void notifyPullMessage(CoapSession session, Queue queue); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 4b62f17f6..29a0fe4de 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.cs.session.loop; import com.alibaba.fastjson.JSONObject; @@ -75,7 +74,6 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ @Resource private DatagramChannelManager datagramChannelManager; - private ScheduledThreadPoolExecutor pullService; private ScheduledThreadPoolExecutor scheduler; private HashedWheelTimer hashedWheelTimer; @@ -91,7 +89,9 @@ public class CoapSessionLoopImpl implements CoapSessionLoop{ public void init() { pullService = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_pull_message_thread_")); scheduler = new ScheduledThreadPoolExecutor(2, new ThreadFactoryImpl("coap_loop_scheduler_")); + // Check new message arrive. pullService.scheduleWithFixedDelay(() -> pullLoop(), pullIntervalMillis, pullIntervalMillis, TimeUnit.MILLISECONDS); + // Check session alive and remove expired session. hashedWheelTimer = new HashedWheelTimer(1, TimeUnit.SECONDS); hashedWheelTimer.start(); Runtime.getRuntime().addShutdownHook(new Thread(() -> { @@ -116,13 +116,12 @@ private void pullLoop() { } } - @Override public void addSession(CoapSession session, CompletableFuture future) { // todo: addSubscriptionAndInit InetSocketAddress address = session.getAddress(); synchronized (this) { - // if this session is already exist, refresh the subscription time and do nothing + // If this session is already exist, refresh the subscription time and do nothing. if (sessionMap.containsKey(address)) { return; } @@ -137,7 +136,7 @@ public void addSession(CoapSession session, CompletableFuture future) { initOffset(session, entry.getKey(), entry.getValue(), future, result); } matchAction.addSubscription(session); - hashedWheelTimer.newTimeout(timeout -> checkSessionAlive(timeout, address), connectConf.getCoapSessionTimeout(), TimeUnit.MILLISECONDS); + hashedWheelTimer.newTimeout(timeout -> checkSessionAlive(timeout, address), connectConf.getCoapSessionTimeout(), TimeUnit.MILLISECONDS); // Add to alive checker. } @Override @@ -152,7 +151,7 @@ public CoapSession removeSession(InetSocketAddress address) { synchronized (this) { session = sessionMap.remove(address); } - // todo: inFlyCache.cleanResource() + // Send the last notification before removal. if (session != null) { session.messageNumIncrement(); CoapMessage removeMessage = new CoapMessage( From 6ca8fe5e4f7a936ba88da0aaeaa382192c0b88f7 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 7 Aug 2024 11:47:58 +0800 Subject: [PATCH 43/67] Add comments and code formatting. --- .../rocketmq/mqtt/cs/session/CoapSession.java | 48 ++++++++++--------- .../mqtt/cs/session/infly/PushAction.java | 3 +- .../cs/session/loop/CoapSessionLoopImpl.java | 4 -- .../upstream/coap/CoapUpstreamProcessor.java | 3 +- .../coap/processor/CoapAckProcessor.java | 1 - .../coap/processor/CoapPublishProcessor.java | 9 ++-- .../processor/CoapSubscribeProcessor.java | 2 - 7 files changed, 34 insertions(+), 36 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index 9d279ff5a..a98538680 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -72,13 +72,14 @@ public void freshQueue(Set queues) { return; } + // Remove expired queues. for (Queue memQueue: offsetMap.keySet()) { if (!queues.contains(memQueue)) { offsetMap.remove(memQueue); } } - // init queueOffset + // Init queueOffset for new queue. for (Queue nowQueue : queues) { if (!offsetMap.containsKey(nowQueue)) { QueueOffset queueOffset = new QueueOffset(); @@ -87,6 +88,7 @@ public void freshQueue(Set queues) { } } + // Remove expired sendingMessage. for (Queue memQueue : sendingMessages.keySet()) { if (!queues.contains(memQueue)) { sendingMessages.remove(memQueue); @@ -98,7 +100,7 @@ public void freshQueue(Set queues) { } } - public void addOffset(Queue queue, QueueOffset offset) { + public void addQueueOffset(Queue queue, QueueOffset offset) { offsetMap.put(queue, offset); } @@ -157,6 +159,7 @@ public boolean sendingMessageIsEmpty(Queue queue) { } } + // Get all messages of the queue which are pending to be sent. public List pendMessageList(Queue queue) { if (queue == null) { throw new RuntimeException("queue is null"); @@ -178,11 +181,12 @@ public List pendMessageList(Queue queue) { return list; } - public void ack(Queue pendingQueue, long pendingDownSeqId) { - if (pendingQueue == null) { + // Set message ack, remove the message from sendingMessages, and update queueOffset of the relative queue. + public void ack(Queue queue, long offset) { + if (queue == null) { throw new RuntimeException("queue is null"); } - LinkedHashSet messages = sendingMessages.get(pendingQueue); + LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { return; } @@ -194,14 +198,14 @@ public void ack(Queue pendingQueue, long pendingDownSeqId) { Iterator iterator = messages.iterator(); while (iterator.hasNext()) { Message message = iterator.next(); - if (message.getOffset() == pendingDownSeqId) { + if (message.getOffset() == offset) { message.setAck(1); } if (message.getAck() != 1) { flag = false; } if (flag) { - updateQueueOffset(pendingQueue, message); + updateQueueOffset(queue, message); // this.markPersistOffsetFlag(true); iterator.remove(); } @@ -209,21 +213,8 @@ public void ack(Queue pendingQueue, long pendingDownSeqId) { } } - public Message nextSendMessageByOrder(Queue queue) { - if (queue == null) { - throw new RuntimeException("queue is null"); - } - LinkedHashSet messages = sendingMessages.get(queue); - if (messages == null) { - return null; - } - synchronized (this) { - return messages.isEmpty() ? null : messages.iterator().next(); - } - } - - public void sendNewMessage(Queue queue, Message messageSend) { - messageNumIncrement(); + // Set message ack, remove the message from sendingMessages, and update queueOffset of the relative queue. + public void ack(Queue queue, Message messageSend) { LinkedHashSet messages = sendingMessages.get(queue); if (messages == null) { return; @@ -246,6 +237,19 @@ public void sendNewMessage(Queue queue, Message messageSend) { } } + public Message nextSendMessageByOrder(Queue queue) { + if (queue == null) { + throw new RuntimeException("queue is null"); + } + LinkedHashSet messages = sendingMessages.get(queue); + if (messages == null) { + return null; + } + synchronized (this) { + return messages.isEmpty() ? null : messages.iterator().next(); + } + } + public int getNextMessageID() { return messageId + messageNum; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index 2b3a3fda4..b827ca37a 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -171,9 +171,10 @@ public void coapPush(Message message, CoapSession session, Queue queue) { message.setPayload("".getBytes()); } - session.sendNewMessage(queue, message); + session.messageNumIncrement(); CoapMessage sendMessage = buildCoapMessage(message, session); datagramChannelManager.pushMessage(session, sendMessage); + session.ack(queue, message); } public void _sendMessage(Session session, String clientId, Subscription subscription, Message message) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 29a0fe4de..4785e5a38 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -188,9 +188,6 @@ private void pullMessage(CoapSession session, Queue queue) { if (session == null || queue == null) { return; } -// if (needLoadPersistedOffset(session, queue)) { -// return; -// } if (!session.sendingMessageIsEmpty(queue)) { scheduler.schedule(() -> pullMessage(session, queue), pullIntervalMillis, TimeUnit.MILLISECONDS); } else { @@ -250,7 +247,6 @@ private void doPull(PullEvent pullEvent) { } } else if (PullResult.PULL_OFFSET_MOVED == pullResult.getCode()) { queueOffset.setOffset(pullResult.getNextQueueOffset().getOffset()); -// session.markPersistOffsetFlag(true); pullMessage(session, queue); } else { logger.error("response:{},{}", session.getAddress(), JSONObject.toJSONString(pullResult)); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java index 7e5072fd4..8c023b619 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/CoapUpstreamProcessor.java @@ -1,4 +1,4 @@ -package org.apache.rocketmq.mqtt.ds.upstream.coap;/* +/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. @@ -14,6 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package org.apache.rocketmq.mqtt.ds.upstream.coap; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.hook.HookResult; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java index e3593f432..7f74d02a7 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapAckProcessor.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java index d46c184e7..6b3e93f83 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapPublishProcessor.java @@ -66,10 +66,9 @@ public CompletableFuture process(CoapRequestMessage msg) { public CompletableFuture put(CoapRequestMessage coapMessage) { // todo: process topic alias - boolean isEmpty = false; - // deal empty payload + // Deal empty payload. if (coapMessage.getPayload() == null || coapMessage.getPayload().length == 0) { coapMessage.setPayload(MessageUtil.EMPTYSTRING.getBytes(StandardCharsets.UTF_8)); isEmpty = true; @@ -78,7 +77,7 @@ public CompletableFuture put(CoapRequestMessage coapMessage) { String originTopic = coapMessage.getTopic(); String pubTopic = TopicUtils.normalizeTopic(originTopic); MqttTopic mqttTopic = TopicUtils.decode(pubTopic); - firstTopicManager.checkFirstTopicIfCreated(mqttTopic.getFirstTopic()); // Check the firstTopic is existed + firstTopicManager.checkFirstTopicIfCreated(mqttTopic.getFirstTopic()); // Check if the firstTopic is existed Set queueNames = wildcardManager.matchQueueSetByMsgTopic(pubTopic, null); // Find queues by topic String msgId = MessageClientIDSetter.createUniqID(); @@ -86,9 +85,9 @@ public CompletableFuture put(CoapRequestMessage coapMessage) { if (coapMessage.isReatin()) { CoapRequestMessage retainedCoapMessage = coapMessage.copy(); - //Change the retained flag of message that will send MQ is 0 + // Change the retained flag of message that will send MQ is 0. retainedCoapMessage.setReatin(false); - // store retained message + // Store retained message. Message metaMessage = MessageUtil.toMessage(retainedCoapMessage); metaMessage.setMsgId(msgId); metaMessage.setBornTimestamp(bornTime); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java index 4c8d2c4c6..88604ad5c 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapSubscribeProcessor.java @@ -14,7 +14,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package org.apache.rocketmq.mqtt.ds.upstream.coap.processor; import org.apache.rocketmq.mqtt.common.hook.HookResult; @@ -37,7 +36,6 @@ public class CoapSubscribeProcessor implements CoapUpstreamProcessor { @Override public CompletableFuture process(CoapRequestMessage coapMessage) { String pubTopic = TopicUtils.normalizeTopic(coapMessage.getTopic()); - assert pubTopic != null; MqttTopic mqttTopic = TopicUtils.decode(pubTopic); firstTopicManager.checkFirstTopicIfCreated(mqttTopic.getFirstTopic()); return HookResult.newHookResult(HookResult.SUCCESS, null, null); From f7fd9b2a8c3d689a74a23b38558d46bcd4d24f44 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Wed, 7 Aug 2024 14:39:18 +0800 Subject: [PATCH 44/67] Add comments and code formatting. --- .../org/apache/rocketmq/mqtt/common/util/MessageUtil.java | 1 - .../rocketmq/mqtt/cs/channel/DatagramChannelManager.java | 2 ++ .../apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java | 2 +- .../rocketmq/mqtt/cs/session/infly/CoapResponseCache.java | 2 +- .../rocketmq/mqtt/cs/session/infly/CoapRetryManager.java | 2 +- .../apache/rocketmq/mqtt/cs/session/infly/PushAction.java | 3 ++- .../org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java | 6 +++--- 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java index 770532fa9..d1d895722 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/MessageUtil.java @@ -150,7 +150,6 @@ public static Message toMessage(CoapRequestMessage coapMessage) { message.setPayload(body); // todo: add other properties, topicAlias, expiry, etc - return message; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java index 0de78aceb..c4b02fe17 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/channel/DatagramChannelManager.java @@ -49,11 +49,13 @@ public void write(CoapMessage message) { channel.writeAndFlush(message); } + // Write to channel and add to response cache. public void writeResponse(CoapMessage message) { channel.writeAndFlush(message); coapResponseCache.put(message); } + // Write to channel and add to retry manager. public void pushMessage(CoapSession session, CoapMessage message) { channel.writeAndFlush(message); if (message.getType() == CoapMessageType.CON) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java index 42f19de85..d5c8bd5c0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/CoapPacketHandler.java @@ -40,4 +40,4 @@ public interface CoapPacketHandler { */ void doHandler(ChannelHandlerContext ctx, T coapMessage, HookResult upstreamHookResult); -} +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java index 049402f15..689e18146 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapResponseCache.java @@ -35,4 +35,4 @@ public CoapMessage get(int messageId) { return responseCache.getIfPresent(messageId); } -} +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java index 9bcfc6d89..1471e234f 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/CoapRetryManager.java @@ -135,4 +135,4 @@ public RetryMessage(int messageId, CoapMessage message, CoapSession session) { } } -} +} \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java index b827ca37a..e376fe542 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/infly/PushAction.java @@ -165,12 +165,13 @@ public void coapPush(Message message, CoapSession session, Queue queue) { logger.error("", e); } - //deal with message with empty payload + // Deal with message with empty payload String msgPayLoad = new String(message.getPayload()); if (msgPayLoad.equals(MessageUtil.EMPTYSTRING) && message.isEmpty()) { message.setPayload("".getBytes()); } + // Send message to client, and remove from the sendingMessages of session. session.messageNumIncrement(); CoapMessage sendMessage = buildCoapMessage(message, session); datagramChannelManager.pushMessage(session, sendMessage); diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java index 096b2df89..d9ce68279 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/notify/NotifyManager.java @@ -283,7 +283,7 @@ public boolean doCoapForward(String node, DatagramPacket packet) { return true; } try { - // serialization + // Serialization, change datagram packet to bytes. ByteBuf buffer = packet.content(); byte[] data = new byte[buffer.readableBytes()]; buffer.getBytes(buffer.readerIndex(), data); @@ -295,11 +295,11 @@ public boolean doCoapForward(String node, DatagramPacket packet) { jsonObject.put("senderPort", sender.getPort()); jsonObject.put("recipientAddress", recipient.getAddress().getHostAddress()); jsonObject.put("recipientPort", recipient.getPort()); - // create command + // Create RPC command. RemotingCommand remotingCommand = RemotingCommand.createRequestCommand(RpcCode.COM_NOTIFY_COAP_MESSAGE, null); remotingCommand.setBody(JSON.toJSONBytes(jsonObject)); - // RPC invoke + // RPC invoke. RemotingCommand response = remotingClient.invokeSync(node + ":" + serviceConf.getCsRpcPort(), remotingCommand, 1000); return response.getCode() == RpcCode.SUCCESS; } catch (Exception e) { From 5b6ea39aaafcd704162d26f7295ed839ddcea1d3 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 8 Aug 2024 14:15:25 +0800 Subject: [PATCH 45/67] Fix bug: code style checking error. --- .../mqtt/common/model/CoapMessage.java | 2 +- .../mqtt/cs/protocol/coap/CoapRPCHandler.java | 44 +++++++++---------- .../coap/handler/CoapSubscribeHandler.java | 4 +- .../rocketmq/mqtt/cs/session/CoapSession.java | 2 +- .../cs/session/loop/CoapSessionLoopImpl.java | 2 +- 5 files changed, 27 insertions(+), 27 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java index d396ef90f..562b61b8b 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/CoapMessage.java @@ -141,7 +141,7 @@ private byte[] intToByteArray(int value) { byte[] byteArray = new byte[3]; byteArray[0] = (byte) (value >> 16); byteArray[1] = (byte) (value >> 8); - byteArray[2] = (byte) (value); + byteArray[2] = (byte) value; return byteArray; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java index 1ae26c94e..1eb500d90 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java @@ -44,28 +44,28 @@ public class CoapRPCHandler extends SimpleChannelInboundHandler @Override protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception { - // Get sender socket address. - InetSocketAddress address = packet.sender(); - // Get all machines. - Set connectorNodes = metaPersistManager.getConnectNodeSet(); - if (connectorNodes == null || connectorNodes.isEmpty()) { - throw new RemotingException("No Connect Nodes"); - } - // Calculate machine index with ip-port to forward the packet. - int hash = address.toString().hashCode(); - int nodeNum = Math.abs(hash % connectorNodes.size()); - List nodeList = new ArrayList<>(connectorNodes); - Collections.sort(nodeList); - String forwardNode = nodeList.get(nodeNum); - // Forward the packet if not for localhost, otherwise fire to next channel. - if (InetAddress.getLocalHost().getHostAddress().equals(forwardNode)) { - ctx.fireChannelRead(packet); - } else { - try { - notifyManager.doCoapForward(forwardNode, packet); - } finally { - ReferenceCountUtil.release(packet); - } + // Get sender socket address. + InetSocketAddress address = packet.sender(); + // Get all machines. + Set connectorNodes = metaPersistManager.getConnectNodeSet(); + if (connectorNodes == null || connectorNodes.isEmpty()) { + throw new RemotingException("No Connect Nodes"); + } + // Calculate machine index with ip-port to forward the packet. + int hash = address.toString().hashCode(); + int nodeNum = Math.abs(hash % connectorNodes.size()); + List nodeList = new ArrayList<>(connectorNodes); + Collections.sort(nodeList); + String forwardNode = nodeList.get(nodeNum); + // Forward the packet if not for localhost, otherwise fire to next channel. + if (InetAddress.getLocalHost().getHostAddress().equals(forwardNode)) { + ctx.fireChannelRead(packet); + } else { + try { + notifyManager.doCoapForward(forwardNode, packet); + } finally { + ReferenceCountUtil.release(packet); } + } } } \ No newline at end of file diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index a7ca03f57..3f86d41a1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -118,7 +118,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, private void sendRetainMessage(CoapSession session) { // Get retainedMessage from persist manager and send to client. CompletableFuture retainedMessage = retainedPersistManager.getRetainedMessage(session.getSubscription().getTopicFilter()); - retainedMessage.whenComplete(((message, throwable) -> { + retainedMessage.whenComplete((message, throwable) -> { if (message == null) { return; } @@ -134,7 +134,7 @@ private void sendRetainMessage(CoapSession session) { session.getAddress() ); datagramChannelManager.pushMessage(session, sendMessage); - })); + }); } public void doResponseFail(CoapRequestMessage coapMessage, String errContent) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java index a98538680..8bd7acad7 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapSession.java @@ -131,7 +131,7 @@ public boolean addSendingMessages(Queue queue, List messages) { logger.warn("not found queueOffset,{},{},{}", address, subscription, queue); return false; } - boolean add =false; + boolean add = false; QueueOffset queueOffset = offsetMap.get(queue); for (Message message : messages) { if (message.getOffset() < queueOffset.getOffset() && queueOffset.getOffset() != Long.MAX_VALUE) { diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java index 4785e5a38..3b1a063d0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/loop/CoapSessionLoopImpl.java @@ -53,7 +53,7 @@ import java.util.concurrent.atomic.AtomicLong; @Component -public class CoapSessionLoopImpl implements CoapSessionLoop{ +public class CoapSessionLoopImpl implements CoapSessionLoop { private static Logger logger = LoggerFactory.getLogger(CoapSessionLoopImpl.class); @Resource From f4e4340ab26e1a702aca88b8a6f1c9370f2e7dd1 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 8 Aug 2024 14:17:16 +0800 Subject: [PATCH 46/67] add test --- .../rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java | 5 ++--- .../mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java | 4 ++++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java index 1ae26c94e..57e1be8d4 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapRPCHandler.java @@ -61,9 +61,8 @@ protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) th if (InetAddress.getLocalHost().getHostAddress().equals(forwardNode)) { ctx.fireChannelRead(packet); } else { - try { - notifyManager.doCoapForward(forwardNode, packet); - } finally { + boolean isForward = notifyManager.doCoapForward(forwardNode, packet); + if (!isForward) { ReferenceCountUtil.release(packet); } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java new file mode 100644 index 000000000..a60b9ecf8 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java @@ -0,0 +1,4 @@ +package org.apache.rocketmq.mqtt.cs.test.protocol.coap; + +public class TestCoapPacketDispatcher { +} From 29f6c204313f470a86ebbb000978b49b9a8598ab Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 8 Aug 2024 15:13:02 +0800 Subject: [PATCH 47/67] Add test. --- .../coap/TestCoapPacketDispatcher.java | 125 ++++++++++++++++++ .../protocol/rpc/TestRpcPacketDispatcher.java | 44 ++++++ 2 files changed, 169 insertions(+) diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java index a60b9ecf8..7dcab02bc 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java @@ -1,4 +1,129 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.embedded.EmbeddedChannel; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapPacketDispatcher; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapPublishHandler; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapSubscribeHandler; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapPublishProcessor; +import org.apache.rocketmq.mqtt.ds.upstream.coap.processor.CoapSubscribeProcessor; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.concurrent.CompletableFuture; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.eq; + +@RunWith(MockitoJUnitRunner.class) public class TestCoapPacketDispatcher { + + @InjectMocks + private CoapPacketDispatcher coapPacketDispatcher; + + private EmbeddedChannel channel; + + @Mock + private CoapRequestMessage msg; + + @Mock + private CoapResponseCache coapResponseCache; + + @Mock + private CoapMessage oldResponse; + + @Mock + private CoapPublishHandler coapPublishHandler; + + @Mock + private CoapPublishProcessor coapPublishProcessor; + + @Mock + private CoapSubscribeHandler coapSubscribeHandler; + + @Mock + private CoapSubscribeProcessor coapSubscribeProcessor; + + @Before + public void setUp() throws IllegalAccessException { + channel = new EmbeddedChannel(coapPacketDispatcher); +// FieldUtils.writeDeclaredField(coapPacketDispatcher, "coapResponseCache", coapResponseCache, true); + + + } + + @Test + public void testRead0Retransmit() { + when(msg.getMessageId()).thenReturn(1); + when(coapResponseCache.get(anyInt())).thenReturn(oldResponse); + // Pass msg into channel and invoke channelRead0() + channel.writeInbound(msg); + // Assert that the response is written to the channel + verify(coapResponseCache).get(1); + Object out = channel.readOutbound(); + assertEquals(oldResponse, out); + assertNull(channel.readOutbound()); + } + + @Test + public void testRead0Publish() { + when(msg.getRequestType()).thenReturn(CoapRequestType.PUBLISH); + when(coapPublishHandler.preHandler(any(ChannelHandlerContext.class), any(CoapRequestMessage.class))).thenReturn(true); + CompletableFuture processResult = new CompletableFuture<>(); + processResult.complete(new HookResult(HookResult.SUCCESS, null, null)); + when(coapPublishProcessor.process(any(CoapRequestMessage.class))).thenReturn(processResult); + + channel.writeInbound(msg); + + verify(coapPublishHandler).preHandler(any(ChannelHandlerContext.class), eq(msg)); + verify(coapPublishProcessor).process(msg); + verify(coapPublishHandler).doHandler(any(ChannelHandlerContext.class), eq(msg), any(HookResult.class)); + assertNull(channel.readOutbound()); + } + + @Test + public void testRead0Subscribe() { + when(msg.getRequestType()).thenReturn(CoapRequestType.SUBSCRIBE); + when(coapSubscribeHandler.preHandler(any(ChannelHandlerContext.class), any(CoapRequestMessage.class))).thenReturn(true); + CompletableFuture processResult = new CompletableFuture<>(); + processResult.complete(new HookResult(HookResult.SUCCESS, null, null)); + when(coapSubscribeProcessor.process(any(CoapRequestMessage.class))).thenReturn(processResult); + + channel.writeInbound(msg); + + verify(coapSubscribeHandler).preHandler(any(ChannelHandlerContext.class), eq(msg)); + verify(coapSubscribeProcessor).process(msg); + verify(coapSubscribeHandler).doHandler(any(ChannelHandlerContext.class), eq(msg), any(HookResult.class)); + assertNull(channel.readOutbound()); + } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/rpc/TestRpcPacketDispatcher.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/rpc/TestRpcPacketDispatcher.java index 548be0221..50c074420 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/rpc/TestRpcPacketDispatcher.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/rpc/TestRpcPacketDispatcher.java @@ -18,10 +18,15 @@ package org.apache.rocketmq.mqtt.cs.test.protocol.rpc; import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.DatagramChannel; +import io.netty.channel.socket.DatagramPacket; import org.apache.commons.lang3.reflect.FieldUtils; import org.apache.rocketmq.mqtt.common.model.RpcCode; import org.apache.rocketmq.mqtt.cs.channel.ChannelManager; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.rpc.RpcPacketDispatcher; import org.apache.rocketmq.mqtt.cs.session.notify.MessageNotifyAction; import org.apache.rocketmq.remoting.protocol.RemotingCommand; @@ -32,17 +37,20 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.nio.charset.StandardCharsets; import java.util.HashSet; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestRpcPacketDispatcher { private RemotingCommand notifyCommand = RemotingCommand.createResponseCommand(RpcCode.CMD_NOTIFY_MQTT_MESSAGE, null); private RemotingCommand closeCommand = RemotingCommand.createResponseCommand(RpcCode.CMD_CLOSE_CHANNEL, null); + private RemotingCommand notifyCoapCommand = RemotingCommand.createResponseCommand(RpcCode.COM_NOTIFY_COAP_MESSAGE, null); private RpcPacketDispatcher packetDispatcher; @@ -52,14 +60,27 @@ public class TestRpcPacketDispatcher { @Mock private ChannelManager channelManager; + @Mock + private DatagramChannelManager datagramChannelManager; + @Mock private ChannelHandlerContext ctx; + @Mock + private DatagramChannel datagramChannel; + + @Mock + private ChannelPipeline pipeline; + + @Mock + private ChannelHandlerContext coapContext; + @Before public void setUp() throws Exception { packetDispatcher = new RpcPacketDispatcher(); FieldUtils.writeDeclaredField(packetDispatcher, "messageNotifyAction", messageNotifyAction, true); FieldUtils.writeDeclaredField(packetDispatcher, "channelManager", channelManager, true); + FieldUtils.writeDeclaredField(packetDispatcher, "datagramChannelManager", datagramChannelManager, true); } @Test @@ -92,4 +113,27 @@ public void testProcessRequestFail() throws Exception { public void testRejectRequest() { Assert.assertFalse(packetDispatcher.rejectRequest()); } + + @Test + public void testProcessRequestNotifyCoap() throws Exception { + when(datagramChannelManager.getChannel()).thenReturn(datagramChannel); + when(datagramChannel.pipeline()).thenReturn(pipeline); + when(pipeline.context("coap-handler")).thenReturn(coapContext); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put("data", new byte[]{1, 2, 3}); + jsonObject.put("senderAddress", "127.0.0.1"); + jsonObject.put("senderPort", 1234); + jsonObject.put("recipientAddress", "192.168.1.1"); + jsonObject.put("recipientPort", 5678); + + notifyCoapCommand.setBody(jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8)); + packetDispatcher.processRequest(ctx, notifyCoapCommand); + + verify(datagramChannelManager).getChannel(); + verify(datagramChannel).pipeline(); + verify(pipeline).context("coap-handler"); + verify(coapContext).fireChannelRead(any(DatagramPacket.class)); + verifyNoMoreInteractions(messageNotifyAction, channelManager, ctx); + } } From d2bacb46b77425901544ba0a079d12b0bce4dfca Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Thu, 8 Aug 2024 17:10:00 +0800 Subject: [PATCH 48/67] Add test --- .../coap/TestCoapPacketDispatcher.java | 3 - .../handler/TestCoapSubscribeHandler.java | 161 ++++++++++++++++++ 2 files changed, 161 insertions(+), 3 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java index 7dcab02bc..dd99e2888 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapPacketDispatcher.java @@ -77,9 +77,6 @@ public class TestCoapPacketDispatcher { @Before public void setUp() throws IllegalAccessException { channel = new EmbeddedChannel(coapPacketDispatcher); -// FieldUtils.writeDeclaredField(coapPacketDispatcher, "coapResponseCache", coapResponseCache, true); - - } @Test diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java new file mode 100644 index 000000000..c0d5e9da1 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java @@ -0,0 +1,161 @@ +package org.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.mqtt.MqttQoS; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.facade.RetainedPersistManager; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapSubscribeHandler; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; + +import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapSubscribeHandler { + + private CoapSubscribeHandler coapSubscribeHandler; + + @Mock + private CoapSessionLoop sessionLoop; + + @Mock + private RetainedPersistManager retainedPersistManager; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Mock + private ChannelHandlerContext ctx; + + @Mock + private CoapRequestMessage coapMessage; + + @Mock + private CoapSession session; + + @Mock + private Channel channel; + + @Before + public void setUp() throws IllegalAccessException { + coapSubscribeHandler = new CoapSubscribeHandler(); + FieldUtils.writeDeclaredField(coapSubscribeHandler, "sessionLoop", sessionLoop, true); + FieldUtils.writeDeclaredField(coapSubscribeHandler, "retainedPersistManager", retainedPersistManager, true); + FieldUtils.writeDeclaredField(coapSubscribeHandler, "datagramChannelManager", datagramChannelManager, true); + } + + @Test + public void testPreHandler() { + boolean result = coapSubscribeHandler.preHandler(ctx, coapMessage); + assertTrue(result); + } + + @Test + public void testDoHandlerUpstreamFail() { + HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); + when(coapMessage.getTokenLength()).thenReturn(0); + when(coapMessage.getMessageId()).thenReturn(1); + when(coapMessage.getToken()).thenReturn(null); + when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); + + coapSubscribeHandler.doHandler(ctx, coapMessage, failHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.INTERNAL_SERVER_ERROR); + return true; + })); + verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); + } + + @Test + public void testDoHanlderOldSession() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapMessage.getQosLevel()).thenReturn(MqttQoS.AT_LEAST_ONCE); + when(coapMessage.getTopic()).thenReturn("topic1/r1"); + when(coapMessage.getType()).thenReturn(CoapMessageType.CON); + when(coapMessage.getTokenLength()).thenReturn(0); + when(coapMessage.getMessageId()).thenReturn(1); + when(coapMessage.getToken()).thenReturn(null); + when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); + when(sessionLoop.getSession(any())).thenReturn(session); + + coapSubscribeHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(sessionLoop).getSession(any()); + verify(session).refreshSubscribeTime(); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.CONTENT); + return true; + })); + verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); + } + + @Test + public void testDoHanlderNewSession() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapMessage.getQosLevel()).thenReturn(MqttQoS.AT_LEAST_ONCE); + when(coapMessage.getTopic()).thenReturn("topic1/r1"); + when(coapMessage.getToken()).thenReturn(null); + when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); + when(sessionLoop.getSession(any())).thenReturn(null); + + coapSubscribeHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(sessionLoop).getSession(any()); + verify(sessionLoop).addSession(any(), any()); + verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); + } + + @Test + public void testDoResponseFail() { + when(coapMessage.getTokenLength()).thenReturn(0); + when(coapMessage.getMessageId()).thenReturn(1); + when(coapMessage.getToken()).thenReturn(null); + when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); + + coapSubscribeHandler.doResponseFail(coapMessage, "Error"); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.INTERNAL_SERVER_ERROR); + return true; + })); + verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); + } + + @Test + public void testDoResponseSuccess() { + when(coapMessage.getType()).thenReturn(CoapMessageType.CON); + when(coapMessage.getTokenLength()).thenReturn(0); + when(coapMessage.getMessageId()).thenReturn(1); + when(coapMessage.getToken()).thenReturn(null); + when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); + when(session.getMessageNum()).thenReturn(1); + + coapSubscribeHandler.doResponseSuccess(coapMessage, session); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.CONTENT); + return true; + })); + verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); + } +} From 387aeb9d53dc3c56f19b80c0330aa2a41b0cfde2 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 9 Aug 2024 17:11:26 +0800 Subject: [PATCH 49/67] Add test --- .../protocol/coap/handler/CoapAckHandler.java | 3 + .../test/protocol/coap/TestCoapDecoder.java | 350 ++++++++++++++++++ .../test/protocol/coap/TestCoapEncoder.java | 107 ++++++ .../protocol/coap/TestCoapRPCHandler.java | 107 ++++++ .../coap/handler/TestCoapAckHandler.java | 85 +++++ .../coap/handler/TestCoapPublishHandler.java | 101 +++++ .../handler/TestCoapSubscribeHandler.java | 78 ++-- 7 files changed, 791 insertions(+), 40 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapDecoder.java create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapEncoder.java create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapRPCHandler.java create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapPublishHandler.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java index 098a6db37..d3d5f5ed1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -38,6 +38,9 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { + if (!upstreamHookResult.isSuccess()) { + return; + } // Ack the relative message in retry manager. int messageId = coapMessage.getMessageId(); if (coapRetryManager.contains(messageId)) { diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapDecoder.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapDecoder.java new file mode 100644 index 000000000..b7b903dfd --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapDecoder.java @@ -0,0 +1,350 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import io.netty.handler.codec.mqtt.MqttQoS; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapDecoder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapDecoder { + + private CoapDecoder coapDecoder; + private DatagramPacket packet; + private List out = new ArrayList<>(); + private InetSocketAddress localAddress = new InetSocketAddress("0.0.0.0", 5683); + private InetSocketAddress remoteAddress = new InetSocketAddress("195.56.3.1", 5683); + + @Mock + private ChannelHandlerContext ctx; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Before + public void setUp() throws Exception{ + coapDecoder = new CoapDecoder(); + FieldUtils.writeDeclaredField(coapDecoder, "datagramChannelManager", datagramChannelManager, true); + } + + @Test + public void testInvalidShortHeader() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_REQUEST); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testInvalidCoapVersion() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0xC0); // Version=3, Type=0, TokenLength=0 + in.writeByte(0x01); // Code=GET + in.writeShort(0x0101); // Message ID=257 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_REQUEST); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testInvalidLongToken() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x49); // Version=1, Type=0, TokenLength=9 + in.writeByte(0x01); // Code=GET + in.writeShort(0x0101); // Message ID=257 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_REQUEST); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testInvalidCoapCode() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x05); // Invalid Code=5 + in.writeShort(0x0101); // Message ID=257 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_REQUEST); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testInvalidToken() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x43); // Version=1, Type=0, TokenLength=3 + in.writeByte(0x01); // Code=GET + in.writeShort(0x0101); // Message ID=257 + in.writeByte(0x01); // Invalid token, shorter then tokenLength + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_REQUEST); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidAck() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x60); // Version=1, Type=2, TokenLength=0 + in.writeByte(0x00); // Code=ACK + in.writeShort(0x0101); // Message ID=257 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(ctx).fireChannelRead(argThat(msg -> { + assertEquals(((CoapRequestMessage) msg).getRequestType(), CoapRequestType.ACK); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testInvalidOptionNumber() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x01); // Code=GET + in.writeShort(0x0101); // Message ID=257 + in.writeByte(0x20); // Invalid Option Number=2 + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.BAD_OPTION); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidSubscribe() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x01); // Code=GET + in.writeShort(0x0101); // Message ID=257 + in.writeByte(0x60); // Option: Observe, 0 + // Construct Option URI-PATH: ps/topic1/r1 + in.writeByte(0x52); // Option: URI-Path, option value length=2 + in.writeBytes("ps".getBytes()); + in.writeByte(0x06); // Option: URI-Path, option value length=6 + in.writeBytes("topic1".getBytes()); + in.writeByte(0x02); // Option: URI-Path, option value length=6 + in.writeBytes("r1".getBytes()); + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.SUBSCRIBE, outMessage.getRequestType()); + assertEquals("topic1/r1", outMessage.getTopic()); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidPublish() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x02); // Code=POST + in.writeShort(0x0101); // Message ID=257 + // Construct Option URI-PATH: ps/topic1/r1 + in.writeByte(0xB2); // Option: URI-Path, option value length=2 + in.writeBytes("ps".getBytes()); + in.writeByte(0x06); // Option: URI-Path, option value length=6 + in.writeBytes("topic1".getBytes()); + in.writeByte(0x02); // Option: URI-Path, option value length=6 + in.writeBytes("r1".getBytes()); + in.writeByte(0xFF); // Payload Marker + in.writeBytes("Hello!".getBytes()); // Payload + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.PUBLISH, outMessage.getRequestType()); + assertEquals("topic1/r1", outMessage.getTopic()); + assertEquals("Hello!", new String(outMessage.getPayload(), StandardCharsets.UTF_8)); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidConnect() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x02); // Code=POST + in.writeShort(0x0101); // Message ID=257 + // Construct Option URI-PATH: mqtt/connection + in.writeByte(0xB4); // Option: URI-Path, option value length=4 + in.writeBytes("mqtt".getBytes()); + in.writeByte(0x0A); // Option: URI-Path, option value length=10 + in.writeBytes("connection".getBytes()); + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.CONNECT, outMessage.getRequestType()); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidDisconnect() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x04); // Code=DELETE + in.writeShort(0x0101); // Message ID=257 + // Construct Option URI-PATH: mqtt/connection + in.writeByte(0xB4); // Option: URI-Path, option value length=4 + in.writeBytes("mqtt".getBytes()); + in.writeByte(0x0A); // Option: URI-Path, option value length=10 + in.writeBytes("connection".getBytes()); + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.DISCONNECT, outMessage.getRequestType()); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidHeartbeat() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x03); // Code=PUT + in.writeShort(0x0101); // Message ID=257 + // Construct Option URI-PATH: mqtt/connection + in.writeByte(0xB4); // Option: URI-Path, option value length=4 + in.writeBytes("mqtt".getBytes()); + in.writeByte(0x0A); // Option: URI-Path, option value length=10 + in.writeBytes("connection".getBytes()); + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.HEARTBEAT, outMessage.getRequestType()); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testValidQuery() { + ByteBuf in = Unpooled.buffer(); + in.writeByte(0x40); // Version=1, Type=0, TokenLength=0 + in.writeByte(0x02); // Code=POST + in.writeShort(0x0101); // Message ID=257 + // Construct Option URI-PATH: ps/topic1/r1 + in.writeByte(0xB2); // Option: URI-Path, option value length=2 + in.writeBytes("ps".getBytes()); + in.writeByte(0x06); // Option: URI-Path, option value length=6 + in.writeBytes("topic1".getBytes()); + in.writeByte(0x02); // Option: URI-Path, option value length=2 + in.writeBytes("r1".getBytes()); + // Construct Option URI-QUERY: clientid=123, qos=1, retain=true, username=admin, password=public + in.writeByte(0x4C); // Option: URI-Query, option value length=12 + in.writeBytes("clientid=123".getBytes()); + in.writeByte(0x05); + in.writeBytes("qos=1".getBytes()); + in.writeByte(0x0B); + in.writeBytes("retain=true".getBytes()); + in.writeByte(0x0B); + in.writeBytes("username=01".getBytes()); + in.writeByte(0x0C); + in.writeBytes("password=111".getBytes()); + in.writeByte(0xFF); // Payload Marker + in.writeBytes("Hello!".getBytes()); // Payload + packet = new DatagramPacket(in, localAddress, remoteAddress); + + coapDecoder.decode(ctx, packet, out); + + assertEquals(1, out.size()); + CoapRequestMessage outMessage = (CoapRequestMessage) out.get(0); + assertEquals(CoapRequestType.PUBLISH, outMessage.getRequestType()); + assertEquals("topic1/r1", outMessage.getTopic()); + assertEquals("123", outMessage.getClientId()); + assertEquals(MqttQoS.AT_LEAST_ONCE, outMessage.getQosLevel()); + assertTrue(outMessage.isReatin()); + assertEquals("01", outMessage.getUserName()); + assertEquals("111", outMessage.getPassword()); + assertEquals("Hello!", new String(outMessage.getPayload(), StandardCharsets.UTF_8)); + + verifyNoMoreInteractions(ctx, datagramChannelManager); + } +} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapEncoder.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapEncoder.java new file mode 100644 index 000000000..bb862af9e --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapEncoder.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapEncoder; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapEncoder { + + private CoapEncoder coapEncoder; + private CoapMessage msg; + private List out = new ArrayList<>(); + + @Mock + private ChannelHandlerContext ctx; + + @Before + public void setUp() throws Exception{ + coapEncoder = new CoapEncoder(); + } + + @Test + public void testEncodeAck() throws Exception { + msg = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + 2, + CoapMessageCode.CREATED, + 1234, + new byte[]{1,2}, + null, + new InetSocketAddress("127.0.0.1", 5683) + ); + + coapEncoder.encode(ctx, msg, out); + + assertEquals(1, out.size()); + ByteBuf buffer = ((DatagramPacket) out.get(0)).content(); + assertEquals(0x62, buffer.readByte()); // version=1, type=2, tokenLength=2 + assertEquals(0x41, buffer.readByte()); // code=65 + assertEquals(1234, buffer.readShort()); // messageID + assertEquals(0x0102, buffer.readShort()); // token + + verifyNoMoreInteractions(ctx); + } + + @Test + public void testEncodeNotify() throws Exception { + msg = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + 2, + CoapMessageCode.CONTENT, + 1234, + new byte[]{1,2}, + "Hello".getBytes(StandardCharsets.UTF_8), + new InetSocketAddress("127.0.0.1", 5683) + ); + + coapEncoder.encode(ctx, msg, out); + + assertEquals(1, out.size()); + ByteBuf buffer = ((DatagramPacket) out.get(0)).content(); + assertEquals(0x42, buffer.readByte()); // version=1, type=0, tokenLength=2 + assertEquals(0x45, buffer.readByte()); // code=69 + assertEquals(1234, buffer.readShort()); // messageID + assertEquals(0x0102, buffer.readShort()); // token + assertEquals(0xFF, buffer.readUnsignedByte()); // payload marker + assertEquals("Hello", buffer.readCharSequence(5, StandardCharsets.UTF_8).toString()); + + verifyNoMoreInteractions(ctx); + } +} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapRPCHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapRPCHandler.java new file mode 100644 index 000000000..9a96b43e2 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/TestCoapRPCHandler.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.socket.DatagramPacket; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.facade.MetaPersistManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.CoapRPCHandler; +import org.apache.rocketmq.mqtt.ds.notify.NotifyManager; +import org.apache.rocketmq.remoting.exception.RemotingException; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.util.HashSet; +import java.util.Set; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapRPCHandler { + + private CoapRPCHandler coapRPCHandler; + + @Mock + private MetaPersistManager metaPersistManager; + + @Mock + private NotifyManager notifyManager; + + @Mock + private ChannelHandlerContext ctx; + + @Mock + private DatagramPacket packet; + + @Before + public void setUp() throws IllegalAccessException { + coapRPCHandler = new CoapRPCHandler(); + FieldUtils.writeDeclaredField(coapRPCHandler, "metaPersistManager", metaPersistManager, true); + FieldUtils.writeDeclaredField(coapRPCHandler, "notifyManager", notifyManager, true); + + when(packet.sender()).thenReturn(new InetSocketAddress("125.45.12.1", 5683)); + } + + @Test + public void testNoConnectNode() throws Exception { + when(metaPersistManager.getConnectNodeSet()).thenReturn(null); + + assertThrows(RemotingException.class, () -> coapRPCHandler.channelRead(ctx, packet)); + verify(metaPersistManager).getConnectNodeSet(); + verifyNoMoreInteractions(metaPersistManager, notifyManager, ctx); + } + + @Test + public void testForLocalhost() throws Exception { + String localAdress = InetAddress.getLocalHost().getHostAddress(); + Set nodes = new HashSet<>(); + nodes.add(localAdress); + when(metaPersistManager.getConnectNodeSet()).thenReturn(nodes); + + coapRPCHandler.channelRead(ctx, packet); + + verify(metaPersistManager).getConnectNodeSet(); + verify(ctx).fireChannelRead(packet); + verifyNoMoreInteractions(metaPersistManager, notifyManager, ctx); + } + + @Test + public void testForwardSuccess() throws Exception { + String forwardAddress = "10.11.123.1"; + Set nodes = new HashSet<>(); + nodes.add(forwardAddress); + when(metaPersistManager.getConnectNodeSet()).thenReturn(nodes); + when(notifyManager.doCoapForward(anyString(), any(DatagramPacket.class))).thenReturn(true); + + coapRPCHandler.channelRead(ctx, packet); + + verify(metaPersistManager).getConnectNodeSet(); + verify(notifyManager).doCoapForward(forwardAddress, packet); + verifyNoMoreInteractions(metaPersistManager, notifyManager, ctx); + } +} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java new file mode 100644 index 000000000..eceadea64 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapAckHandler; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; + +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapAckHandler { + + private CoapAckHandler coapAckHandler; + + @Mock + private CoapRetryManager coapRetryManager; + + @Mock + private ChannelHandlerContext ctx; + + private CoapRequestMessage coapMessage; + + @Before + public void setUp() throws IllegalAccessException { + coapAckHandler = new CoapAckHandler(); + FieldUtils.writeDeclaredField(coapAckHandler, "coapRetryManager", coapRetryManager, true); + coapMessage = new CoapRequestMessage( + 1, + CoapMessageType.ACK, + 0, + CoapMessageCode.EMPTY, + 1111, + null, + null, + new InetSocketAddress("127.0.0.1", 9675) + ); + } + + @Test + public void testPreHandler() { + boolean result = coapAckHandler.preHandler(ctx, coapMessage); + assertTrue(result); + } + + @Test + public void testAckSuccess() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapRetryManager.contains(anyInt())).thenReturn(true); + + coapAckHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapRetryManager).contains(anyInt()); + verify(coapRetryManager).ackMessage(anyInt()); + verifyNoMoreInteractions(ctx, coapRetryManager); + } +} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapPublishHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapPublishHandler.java new file mode 100644 index 000000000..5eeb7bff0 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapPublishHandler.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang3.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapPublishHandler; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapPublishHandler { + + private CoapPublishHandler coapPublishHandler; + private CoapRequestMessage coapMessage; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Mock + private ChannelHandlerContext ctx; + + @Before + public void setUp() throws IllegalAccessException { + coapPublishHandler = new CoapPublishHandler(); + FieldUtils.writeDeclaredField(coapPublishHandler, "datagramChannelManager", datagramChannelManager, true); + coapMessage = new CoapRequestMessage( + 1, + CoapMessageType.CON, + 0, + CoapMessageCode.POST, + 1111, + null, + "TestData".getBytes(StandardCharsets.UTF_8), + new InetSocketAddress("127.0.0.1", 9675) + ); + } + + @Test + public void testPreHandler() { + boolean result = coapPublishHandler.preHandler(ctx, coapMessage); + assertTrue(result); + } + + @Test + public void testDoHandlerUpstreamFail() { + HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); + + coapPublishHandler.doHandler(ctx, coapMessage, failHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.INTERNAL_SERVER_ERROR); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } + + @Test + public void testDoHandlerSuccess() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + + coapPublishHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(response.getCode(), CoapMessageCode.CREATED); + return true; + })); + verifyNoMoreInteractions(ctx, datagramChannelManager); + } +} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java index c0d5e9da1..16ff02a0a 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java @@ -1,6 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttQoS; import org.apache.commons.lang3.reflect.FieldUtils; @@ -25,14 +40,18 @@ import org.mockito.junit.MockitoJUnitRunner; import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestCoapSubscribeHandler { private CoapSubscribeHandler coapSubscribeHandler; + private CoapRequestMessage coapMessage; @Mock private CoapSessionLoop sessionLoop; @@ -46,21 +65,27 @@ public class TestCoapSubscribeHandler { @Mock private ChannelHandlerContext ctx; - @Mock - private CoapRequestMessage coapMessage; - @Mock private CoapSession session; - @Mock - private Channel channel; - @Before public void setUp() throws IllegalAccessException { coapSubscribeHandler = new CoapSubscribeHandler(); FieldUtils.writeDeclaredField(coapSubscribeHandler, "sessionLoop", sessionLoop, true); FieldUtils.writeDeclaredField(coapSubscribeHandler, "retainedPersistManager", retainedPersistManager, true); FieldUtils.writeDeclaredField(coapSubscribeHandler, "datagramChannelManager", datagramChannelManager, true); + coapMessage = new CoapRequestMessage( + 1, + CoapMessageType.CON, + 0, + CoapMessageCode.GET, + 1111, + null, + "TestData".getBytes(StandardCharsets.UTF_8), + new InetSocketAddress("127.0.0.1", 9675) + ); + coapMessage.setTopic("topic1/r1"); + coapMessage.setQosLevel(MqttQoS.AT_LEAST_ONCE); } @Test @@ -72,10 +97,6 @@ public void testPreHandler() { @Test public void testDoHandlerUpstreamFail() { HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); - when(coapMessage.getTokenLength()).thenReturn(0); - when(coapMessage.getMessageId()).thenReturn(1); - when(coapMessage.getToken()).thenReturn(null); - when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); coapSubscribeHandler.doHandler(ctx, coapMessage, failHookResult); @@ -89,18 +110,11 @@ public void testDoHandlerUpstreamFail() { @Test public void testDoHanlderOldSession() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapMessage.getQosLevel()).thenReturn(MqttQoS.AT_LEAST_ONCE); - when(coapMessage.getTopic()).thenReturn("topic1/r1"); - when(coapMessage.getType()).thenReturn(CoapMessageType.CON); - when(coapMessage.getTokenLength()).thenReturn(0); - when(coapMessage.getMessageId()).thenReturn(1); - when(coapMessage.getToken()).thenReturn(null); - when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); - when(sessionLoop.getSession(any())).thenReturn(session); + when(sessionLoop.getSession(any(InetSocketAddress.class))).thenReturn(session); coapSubscribeHandler.doHandler(ctx, coapMessage, successHookResult); - verify(sessionLoop).getSession(any()); + verify(sessionLoop).getSession(any(InetSocketAddress.class)); verify(session).refreshSubscribeTime(); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(response.getCode(), CoapMessageCode.CONTENT); @@ -112,26 +126,17 @@ public void testDoHanlderOldSession() { @Test public void testDoHanlderNewSession() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapMessage.getQosLevel()).thenReturn(MqttQoS.AT_LEAST_ONCE); - when(coapMessage.getTopic()).thenReturn("topic1/r1"); - when(coapMessage.getToken()).thenReturn(null); - when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); - when(sessionLoop.getSession(any())).thenReturn(null); + when(sessionLoop.getSession(any(InetSocketAddress.class))).thenReturn(null); coapSubscribeHandler.doHandler(ctx, coapMessage, successHookResult); - verify(sessionLoop).getSession(any()); - verify(sessionLoop).addSession(any(), any()); + verify(sessionLoop).getSession(any(InetSocketAddress.class)); + verify(sessionLoop).addSession(any(CoapSession.class), any()); verifyNoMoreInteractions(ctx, sessionLoop, retainedPersistManager, datagramChannelManager); } @Test public void testDoResponseFail() { - when(coapMessage.getTokenLength()).thenReturn(0); - when(coapMessage.getMessageId()).thenReturn(1); - when(coapMessage.getToken()).thenReturn(null); - when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); - coapSubscribeHandler.doResponseFail(coapMessage, "Error"); verify(datagramChannelManager).writeResponse(argThat(response -> { @@ -143,13 +148,6 @@ public void testDoResponseFail() { @Test public void testDoResponseSuccess() { - when(coapMessage.getType()).thenReturn(CoapMessageType.CON); - when(coapMessage.getTokenLength()).thenReturn(0); - when(coapMessage.getMessageId()).thenReturn(1); - when(coapMessage.getToken()).thenReturn(null); - when(coapMessage.getRemoteAddress()).thenReturn(new InetSocketAddress("127.0.0.1", 9675)); - when(session.getMessageNum()).thenReturn(1); - coapSubscribeHandler.doResponseSuccess(coapMessage, session); verify(datagramChannelManager).writeResponse(argThat(response -> { From 73a0e662fa41e798ed10d197b78f8768945c22ad Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 9 Aug 2024 17:52:19 +0800 Subject: [PATCH 50/67] Add test --- .../channel/TestDatagramChannelManager.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/channel/TestDatagramChannelManager.java diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/channel/TestDatagramChannelManager.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/channel/TestDatagramChannelManager.java new file mode 100644 index 000000000..e1782b93c --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/channel/TestDatagramChannelManager.java @@ -0,0 +1,91 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.channel; + +import io.netty.channel.socket.DatagramChannel; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.session.CoapSession; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapResponseCache; +import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TestDatagramChannelManager { + + private DatagramChannelManager datagramChannelManager; + + @Mock + private CoapResponseCache coapResponseCache; + + @Mock + private CoapRetryManager coapRetryManager; + + @Mock + private DatagramChannel channel; + + @Mock + private CoapMessage message; + + @Mock + private CoapSession session; + + @Before + public void setUp() throws Exception{ + datagramChannelManager = new DatagramChannelManager(); + FieldUtils.writeDeclaredField(datagramChannelManager, "coapResponseCache", coapResponseCache, true); + FieldUtils.writeDeclaredField(datagramChannelManager, "coapRetryManager", coapRetryManager, true); + FieldUtils.writeDeclaredField(datagramChannelManager, "channel", channel, true); + } + + @Test + public void testWrite() { + datagramChannelManager.write(message); + verify(channel).writeAndFlush(message); + verifyNoMoreInteractions(coapResponseCache, coapRetryManager, channel); + } + + @Test + public void testWriteResponse() { + datagramChannelManager.writeResponse(message); + verify(channel).writeAndFlush(message); + verify(coapResponseCache).put(message); + verifyNoMoreInteractions(coapResponseCache, coapRetryManager, channel); + } + + @Test + public void testPushMessage() { + when(message.getType()).thenReturn(CoapMessageType.CON); + + datagramChannelManager.pushMessage(session, message); + verify(channel).writeAndFlush(message); + verify(coapRetryManager).addRetryMessage(session, message); + verifyNoMoreInteractions(coapResponseCache, coapRetryManager, channel); + } + + +} From 477a3832b68810c32dc770505fb010a31e619f93 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 9 Aug 2024 18:16:00 +0800 Subject: [PATCH 51/67] Add coapTokenManager, but has not check yet. --- .../mqtt/cs/session/CoapTokenManager.java | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java new file mode 100644 index 000000000..513ce10eb --- /dev/null +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session; + +import org.apache.rocketmq.common.ThreadFactoryImpl; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +@Component +public class CoapTokenManager { + + private ScheduledThreadPoolExecutor scheduler; + + private ConcurrentMap tokenMap = new ConcurrentHashMap<>(1024); + + private static final int SCHEDULE_INTERVAL = 1000; + private static final long TIMEOUT = 3000; + + @PostConstruct + public void init() { + scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_token_alive_thread_")); + scheduler.scheduleWithFixedDelay(this::clearExpiredToken, SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); + } + + public String createToken(String clientId) { + CoapToken coapToken = new CoapToken(clientId); + tokenMap.put(clientId, coapToken); + return coapToken.token; + } + + public void removeToken(String clientId) { + tokenMap.remove(clientId); + } + + public String getToken(String clientId) { + CoapToken coapToken = tokenMap.get(clientId); + return coapToken == null ? null : coapToken.token; + } + + public boolean isValid(String clientId, String token) { + CoapToken coapToken = tokenMap.get(clientId); + if (coapToken == null) { + return false; + } + return coapToken.token.equals(token); + } + + public void refreshToken(String clientId) { + CoapToken coapToken = tokenMap.get(clientId); + if (coapToken != null) { + coapToken.lastUpdateTime = System.currentTimeMillis(); + } + } + + private void clearExpiredToken() { + if (tokenMap.isEmpty()) { + return; + } + for (CoapToken token : tokenMap.values()) { + if (System.currentTimeMillis() - token.lastUpdateTime > TIMEOUT) { + tokenMap.remove(token.clientId); + } + } + } + + public class CoapToken { + private String clientId; + private String token = UUID.randomUUID().toString().replace("-", "").substring(0, 10); + private long lastUpdateTime = System.currentTimeMillis(); + + public CoapToken(String clientId) { + this.clientId = clientId; + } + } + +} From d99553d129f21f76abeb3eeb0eb977ee210f556a Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 12 Aug 2024 10:13:51 +0800 Subject: [PATCH 52/67] Add test to CoapTokenManager. --- .../mqtt/cs/session/CoapTokenManager.java | 8 ++ .../cs/test/session/TestCoapTokenManager.java | 73 +++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java index 513ce10eb..db4e602c1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java @@ -72,6 +72,14 @@ public void refreshToken(String clientId) { } } + public ConcurrentMap getTokenMap() { + return tokenMap; + } + + public void setTokenMap(ConcurrentMap tokenMap) { + this.tokenMap = tokenMap; + } + private void clearExpiredToken() { if (tokenMap.isEmpty()) { return; diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java new file mode 100644 index 000000000..82fadaf3c --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.session; + +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapTokenManager { + + private CoapTokenManager coapTokenManager; + + @Before + public void setUp() { + coapTokenManager = new CoapTokenManager(); + } + + @Test + public void testCreateToken() { + String clientId = "test111"; + String token = coapTokenManager.createToken(clientId); + + assertEquals(1, coapTokenManager.getTokenMap().size()); + assertEquals(token, coapTokenManager.getToken(clientId)); + } + + @Test + public void testRemoveToken() { + String clientId = "test111"; + + String token = coapTokenManager.createToken(clientId); + assertEquals(1, coapTokenManager.getTokenMap().size()); + + coapTokenManager.removeToken(clientId); + assertEquals(0, coapTokenManager.getTokenMap().size()); + } + + @Test + public void testIsValid() { + String clientId = "test111"; + String token = coapTokenManager.createToken(clientId); + assertTrue(coapTokenManager.isValid(clientId, token)); + } + + @Test + public void testNotValid() { + String clientId = "test111"; + String token = coapTokenManager.createToken(clientId); + assertFalse(coapTokenManager.isValid(clientId, "wrongToken")); + } + +} From 7e20f7be423d5d3ff582458ee5b56f10d3aed1d8 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 12 Aug 2024 10:54:43 +0800 Subject: [PATCH 53/67] Add CoapConnectHandler and relative test. --- .../coap/handler/CoapConnectHandler.java | 49 ++++++- .../coap/handler/TestCoapConnectHandler.java | 120 ++++++++++++++++++ .../handler/TestCoapSubscribeHandler.java | 3 +- 3 files changed, 167 insertions(+), 5 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index cdd7d9427..a9ab1912d 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -18,22 +18,63 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + @Component public class CoapConnectHandler implements CoapPacketHandler { + @Resource + private CoapTokenManager coapTokenManager; + + @Resource + private DatagramChannelManager datagramChannelManager; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + if (coapMessage.getClientId() == null || coapMessage.getUserName() == null || coapMessage.getPassword() == null) { + return false; + } // todo: check auth - // todo: check client id duplication - return false; + return true; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - // todo: response ack and return token + // Response fail ack if upstream hook fail. + if (!upstreamHookResult.isSuccess()) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId(), + coapMessage.getToken(), + upstreamHookResult.getRemark().getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return; + } + // Create new token. + String authToken = coapTokenManager.createToken(coapMessage.getClientId()); + // Response success ack and return authToken. + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.CREATED, + coapMessage.getMessageId(), + coapMessage.getToken(), + authToken.getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java new file mode 100644 index 000000000..20228301c --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapConnectHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapConnectHandler { + + private CoapConnectHandler coapConnectHandler; + private CoapRequestMessage coapMessage; + + @Mock + private CoapTokenManager coapTokenManager; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Mock + private ChannelHandlerContext ctx; + + @Before + public void setUp() throws Exception { + coapConnectHandler = new CoapConnectHandler(); + FieldUtils.writeDeclaredField(coapConnectHandler, "coapTokenManager", coapTokenManager, true); + FieldUtils.writeDeclaredField(coapConnectHandler, "datagramChannelManager", datagramChannelManager, true); + coapMessage = new CoapRequestMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + 0, + CoapMessageCode.POST, + 1111, + null, + null, + new InetSocketAddress("127.0.0.1", 9675) + ); + coapMessage.setRequestType(CoapRequestType.CONNECT); + coapMessage.setClientId("123"); + coapMessage.setUserName("admin"); + coapMessage.setPassword("public"); + } + + @Test + public void testPreHandler() { + assertTrue(coapConnectHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + + coapMessage.setClientId(null); + assertFalse(coapConnectHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testConnectFail() { + HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); + + coapConnectHandler.doHandler(ctx, coapMessage, failHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.INTERNAL_SERVER_ERROR, response.getCode()); + assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testConnectSuccess() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + String authToken = "12345678"; + when(coapTokenManager.createToken(anyString())).thenReturn(authToken); + + coapConnectHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapTokenManager).createToken(coapMessage.getClientId()); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.CREATED, response.getCode()); + assertEquals(authToken, new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } +} \ No newline at end of file diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java index 16ff02a0a..18e2f8eab 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapSubscribeHandler.java @@ -24,6 +24,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.Constants; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapSubscribeHandler; @@ -75,7 +76,7 @@ public void setUp() throws IllegalAccessException { FieldUtils.writeDeclaredField(coapSubscribeHandler, "retainedPersistManager", retainedPersistManager, true); FieldUtils.writeDeclaredField(coapSubscribeHandler, "datagramChannelManager", datagramChannelManager, true); coapMessage = new CoapRequestMessage( - 1, + Constants.COAP_VERSION, CoapMessageType.CON, 0, CoapMessageCode.GET, From 807ccb50f4211f83451827aef363fcf892a04c8d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 12 Aug 2024 11:27:16 +0800 Subject: [PATCH 54/67] Add CoapDisconnectHandler and relative test. --- .../coap/handler/CoapConnectHandler.java | 2 +- .../coap/handler/CoapDisconnectHandler.java | 64 ++++++++- .../coap/handler/TestCoapConnectHandler.java | 2 +- .../handler/TestCoapDisconnectHandler.java | 135 ++++++++++++++++++ 4 files changed, 197 insertions(+), 6 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index a9ab1912d..e08b50990 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -53,7 +53,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, Constants.COAP_VERSION, CoapMessageType.ACK, coapMessage.getTokenLength(), - CoapMessageCode.INTERNAL_SERVER_ERROR, + CoapMessageCode.UNAUTHORIZED, coapMessage.getMessageId(), coapMessage.getToken(), upstreamHookResult.getRemark().getBytes(StandardCharsets.UTF_8), diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index 053f0024e..f28971e44 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -18,22 +18,78 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + @Component public class CoapDisconnectHandler implements CoapPacketHandler { + @Resource + private CoapTokenManager coapTokenManager; + + @Resource + private DatagramChannelManager datagramChannelManager; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { // todo: check auth - // todo: check client id token - return false; + if (coapMessage.getClientId() == null || coapMessage.getAuthToken() == null) { + return false; + } + return true; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - // todo: response ack + // Response fail ack if upstream hook fail. + if (!upstreamHookResult.isSuccess()) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId(), + coapMessage.getToken(), + upstreamHookResult.getRemark().getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return; + } + // Response unauthorized ack if authToken is not valid. + if (!coapTokenManager.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.UNAUTHORIZED, + coapMessage.getMessageId(), + coapMessage.getToken(), + "AuthToken is not valid.".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return; + } + // Remove clientId-token from token manager. + coapTokenManager.removeToken(coapMessage.getClientId()); + // Response ack success + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.DELETED, + coapMessage.getMessageId(), + coapMessage.getToken(), + null, + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java index 20228301c..dd335bceb 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java @@ -94,7 +94,7 @@ public void testConnectFail() { coapConnectHandler.doHandler(ctx, coapMessage, failHookResult); verify(datagramChannelManager).writeResponse(argThat(response -> { - assertEquals(CoapMessageCode.INTERNAL_SERVER_ERROR, response.getCode()); + assertEquals(CoapMessageCode.UNAUTHORIZED, response.getCode()); assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java new file mode 100644 index 000000000..3e513aaf1 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapDisconnectHandler { + + private CoapDisconnectHandler coapDisconnectHandler; + private CoapRequestMessage coapMessage; + + @Mock + private CoapTokenManager coapTokenManager; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Mock + private ChannelHandlerContext ctx; + + @Before + public void setUp() throws Exception { + coapDisconnectHandler = new CoapDisconnectHandler(); + FieldUtils.writeDeclaredField(coapDisconnectHandler, "coapTokenManager", coapTokenManager, true); + FieldUtils.writeDeclaredField(coapDisconnectHandler, "datagramChannelManager", datagramChannelManager, true); + coapMessage = new CoapRequestMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + 0, + CoapMessageCode.DELETED, + 1111, + null, + null, + new InetSocketAddress("127.0.0.1", 9675) + ); + coapMessage.setRequestType(CoapRequestType.DISCONNECT); + coapMessage.setClientId("123"); + coapMessage.setAuthToken("12345678"); + } + + @Test + public void testPreHandler() { + assertTrue(coapDisconnectHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + + coapMessage.setClientId(null); + assertFalse(coapDisconnectHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testDisconnectFail() { + HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); + + coapDisconnectHandler.doHandler(ctx, coapMessage, failHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.INTERNAL_SERVER_ERROR, response.getCode()); + assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testDisconnectUnauthorized() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(false); + + coapDisconnectHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.UNAUTHORIZED, response.getCode()); + assertEquals("AuthToken is not valid.", new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testDisconnectSuccess() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(true); + + coapDisconnectHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + verify(coapTokenManager).removeToken(coapMessage.getClientId()); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.DELETED, response.getCode()); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } +} From ae2d6e2ca36761b12dd159eca7d6cd57edb9388d Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 12 Aug 2024 11:37:54 +0800 Subject: [PATCH 55/67] Add CoapHeartbeatHandler and relative test. modify * imports. --- .../coap/handler/CoapHeartbeatHandler.java | 64 +++++++- .../coap/handler/TestCoapConnectHandler.java | 6 +- .../handler/TestCoapDisconnectHandler.java | 6 +- .../handler/TestCoapHeartbeatHandler.java | 138 ++++++++++++++++++ 4 files changed, 208 insertions(+), 6 deletions(-) create mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java index b47348ad8..d0f02490c 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -18,22 +18,78 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.springframework.stereotype.Component; +import javax.annotation.Resource; +import java.nio.charset.StandardCharsets; + @Component public class CoapHeartbeatHandler implements CoapPacketHandler { + @Resource + private CoapTokenManager coapTokenManager; + + @Resource + private DatagramChannelManager datagramChannelManager; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { // todo: check auth - // todo: check client id and token - return false; + if (coapMessage.getClientId() == null || coapMessage.getAuthToken() == null) { + return false; + } + return true; } @Override public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, HookResult upstreamHookResult) { - // todo: response ack + // Response fail ack if upstream hook fail. + if (!upstreamHookResult.isSuccess()) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.INTERNAL_SERVER_ERROR, + coapMessage.getMessageId(), + coapMessage.getToken(), + upstreamHookResult.getRemark().getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return; + } + // Response unauthorized ack if authToken is not valid. + if (!coapTokenManager.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.UNAUTHORIZED, + coapMessage.getMessageId(), + coapMessage.getToken(), + "AuthToken is not valid.".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return; + } + // Refresh update time of token + coapTokenManager.refreshToken(coapMessage.getClientId()); + // Response ack success + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.CHANGED, + coapMessage.getMessageId(), + coapMessage.getToken(), + null, + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java index dd335bceb..ad79aa3ba 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java @@ -19,7 +19,11 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang.reflect.FieldUtils; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapConnectHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java index 3e513aaf1..a7b5725b3 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java @@ -19,7 +19,11 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.commons.lang.reflect.FieldUtils; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java new file mode 100644 index 000000000..fe442f071 --- /dev/null +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.protocol.coap.handler; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapHeartbeatHandler; +import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapHeartbeatHandler { + + private CoapHeartbeatHandler coapHeartbeatHandler; + private CoapRequestMessage coapMessage; + + @Mock + private CoapTokenManager coapTokenManager; + + @Mock + private DatagramChannelManager datagramChannelManager; + + @Mock + private ChannelHandlerContext ctx; + + @Before + public void setUp() throws Exception { + coapHeartbeatHandler = new CoapHeartbeatHandler(); + FieldUtils.writeDeclaredField(coapHeartbeatHandler, "coapTokenManager", coapTokenManager, true); + FieldUtils.writeDeclaredField(coapHeartbeatHandler, "datagramChannelManager", datagramChannelManager, true); + coapMessage = new CoapRequestMessage( + Constants.COAP_VERSION, + CoapMessageType.CON, + 0, + CoapMessageCode.PUT, + 1111, + null, + null, + new InetSocketAddress("127.0.0.1", 9675) + ); + coapMessage.setRequestType(CoapRequestType.HEARTBEAT); + coapMessage.setClientId("123"); + coapMessage.setAuthToken("12345678"); + } + + @Test + public void testPreHandler() { + assertTrue(coapHeartbeatHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + + coapMessage.setClientId(null); + assertFalse(coapHeartbeatHandler.preHandler(ctx, coapMessage)); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testHeartbeatFail() { + HookResult failHookResult = new HookResult(HookResult.FAIL, "Error", null); + + coapHeartbeatHandler.doHandler(ctx, coapMessage, failHookResult); + + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.INTERNAL_SERVER_ERROR, response.getCode()); + assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testHeartbeatUnauthorized() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(false); + + coapHeartbeatHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.UNAUTHORIZED, response.getCode()); + assertEquals("AuthToken is not valid.", new String(response.getPayload(), StandardCharsets.UTF_8)); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } + + @Test + public void testHeartbeatSuccess() { + HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); + when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(true); + + coapHeartbeatHandler.doHandler(ctx, coapMessage, successHookResult); + + verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + verify(coapTokenManager).refreshToken(coapMessage.getClientId()); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.CHANGED, response.getCode()); + return true; + })); + verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + } +} From 34c9e6a02e598d8479552cfeceb23bc82962da61 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 13 Aug 2024 14:27:57 +0800 Subject: [PATCH 56/67] Add PasswordHashUtil and test. --- .../mqtt/common/util/PasswordHashUtil.java | 64 ++++++++++++++++++ .../test/util/TestPasswordHashUtil.java | 67 +++++++++++++++++++ 2 files changed, 131 insertions(+) create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java create mode 100644 mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestPasswordHashUtil.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java new file mode 100644 index 000000000..62ac7adf5 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.util; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +public class PasswordHashUtil { + + // Generate random salt. + public static String generateSalt(int length) { + byte[] salt = new byte[length]; + new SecureRandom().nextBytes(salt); + return Base64.getEncoder().encodeToString(salt); + } + + // Hash with salt, algorithm must be String of "MD5"/"SHA"/"SHA-256"/"SHA-512". + public static String hashWithSalt(String password, String salt, String algorithm, String saltPosition) throws NoSuchAlgorithmException { + MessageDigest md = MessageDigest.getInstance(algorithm); + switch (saltPosition) { + case "prefix": + md.update(salt.getBytes(StandardCharsets.UTF_8)); + md.update(password.getBytes(StandardCharsets.UTF_8)); + break; + case "suffix": + md.update(password.getBytes(StandardCharsets.UTF_8)); + md.update(salt.getBytes(StandardCharsets.UTF_8)); + break; + case "disable": + md.update(password.getBytes(StandardCharsets.UTF_8)); + default: + throw new IllegalArgumentException("Invalidsalt position: " + saltPosition); + } + byte[] hashedPassword = md.digest(); + return Base64.getEncoder().encodeToString(hashedPassword); + } + + + public static boolean validatePassword(String password, String storedHash, String salt, String algorithm, String saltPosition) throws Exception{ + if (algorithm.equals("PLAIN")) { + return password.equals(storedHash); + } + return hashWithSalt(password, salt, algorithm, saltPosition).equals(storedHash); + } + + +} diff --git a/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestPasswordHashUtil.java b/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestPasswordHashUtil.java new file mode 100644 index 000000000..44e7f5928 --- /dev/null +++ b/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestPasswordHashUtil.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.test.util; + +import org.apache.rocketmq.mqtt.common.util.PasswordHashUtil; +import org.junit.Test; + +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TestPasswordHashUtil { + + @Test + public void testGenerateSalt() { + int saltLength = 16; + String salt = PasswordHashUtil.generateSalt(saltLength); + assertEquals(saltLength, Base64.getDecoder().decode(salt).length); + } + + @Test + public void testHashWithSalt() throws NoSuchAlgorithmException { + String password = "password"; + String salt = PasswordHashUtil.generateSalt(16); + String algorithm = "SHA-256"; + String saltPosition = "suffix"; + + String hashPassword = PasswordHashUtil.hashWithSalt(password, salt, algorithm, saltPosition); + assertNotNull(hashPassword); + assertNotEquals(password, hashPassword); + + String hashPassword2 = PasswordHashUtil.hashWithSalt(password, salt, algorithm, saltPosition); + assertEquals(hashPassword, hashPassword2); + } + + @Test + public void testValidatePasswordPlain() throws Exception { + String password = "password"; + assertTrue(PasswordHashUtil.validatePassword(password, password, null, "PLAIN", null)); + } + + @Test + public void testValidatePasswordHash() throws Exception { + String password = "password"; + String salt = PasswordHashUtil.generateSalt(16); + String hashPassword = PasswordHashUtil.hashWithSalt(password, salt, "SHA-256", "suffix"); + assertTrue(PasswordHashUtil.validatePassword(password, hashPassword, salt, "SHA-256", "suffix")); + } +} From be731dee7d40b844513dc8af4bb3cf9209cb49e7 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 13 Aug 2024 15:06:53 +0800 Subject: [PATCH 57/67] Add CoapAuthManager and relative test. --- .../mqtt/common/util/PasswordHashUtil.java | 2 +- .../mqtt/ds/auth/CoapAuthManager.java | 58 ++++++++++++++++ .../ds/test/auth/TestCoapAuthManager.java | 69 +++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java create mode 100644 mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java index 62ac7adf5..4bcd3ab7f 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java @@ -53,7 +53,7 @@ public static String hashWithSalt(String password, String salt, String algorithm } - public static boolean validatePassword(String password, String storedHash, String salt, String algorithm, String saltPosition) throws Exception{ + public static boolean validatePassword(String password, String storedHash, String salt, String algorithm, String saltPosition) throws NoSuchAlgorithmException{ if (algorithm.equals("PLAIN")) { return password.equals(storedHash); } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java new file mode 100644 index 000000000..c98dbfccd --- /dev/null +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java @@ -0,0 +1,58 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.auth; + +import org.apache.rocketmq.mqtt.common.util.PasswordHashUtil; +import org.apache.rocketmq.mqtt.ds.config.ServiceConf; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.security.NoSuchAlgorithmException; + +@Component +public class CoapAuthManager { + public static Logger logger = LoggerFactory.getLogger(CoapAuthManager.class); + + private static String salt; + private static final String algorithm = "SHA-256"; + private static final String saltPosition = "suffix"; + private static String hashedPassword; + + @Resource + private ServiceConf serviceConf; + + @PostConstruct + public void init() throws NoSuchAlgorithmException { + salt = PasswordHashUtil.generateSalt(16); + hashedPassword = PasswordHashUtil.hashWithSalt(serviceConf.getSecretKey(), salt, algorithm, saltPosition); + } + + public boolean doAuth(String username, String password) { + try { + if (serviceConf.getUsername().equals(username) && PasswordHashUtil.validatePassword(password, hashedPassword, salt, algorithm, saltPosition)) { + return true; + } + } catch (NoSuchAlgorithmException e) { + logger.error("", e); + } + return false; + } + +} diff --git a/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java b/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java new file mode 100644 index 000000000..eb34202cd --- /dev/null +++ b/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.ds.test.auth; + +import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.ds.auth.CoapAuthManager; +import org.apache.rocketmq.mqtt.ds.config.ServiceConf; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import static org.junit.Assert.assertFalse; + +@RunWith(MockitoJUnitRunner.class) +public class TestCoapAuthManager { + private static final String username = "user111"; + private static final String secretKey = "password111"; + + private CoapAuthManager coapAuthManager; + + @Mock + private ServiceConf serviceConf; + + @Before + public void setUp() throws Exception { + coapAuthManager = new CoapAuthManager(); + FieldUtils.writeDeclaredField(coapAuthManager, "serviceConf", serviceConf, true); + + when(serviceConf.getUsername()).thenReturn(username); + when(serviceConf.getSecretKey()).thenReturn(secretKey); + + coapAuthManager.init(); + } + + @Test + public void doAuthWrongUsername() { + String wrongUsername = "user222"; + assertFalse(coapAuthManager.doAuth(wrongUsername, secretKey)); + } + + @Test + public void doAuthWrongPassword() { + String wrongPassword = "password222"; + assertFalse(coapAuthManager.doAuth(username, wrongPassword)); + } + + @Test + public void doAuthSuccess() { + assertTrue(coapAuthManager.doAuth(username, secretKey)); + } +} From fe553099c5a9ec5e09c4f0e80584b3141a7823fc Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 13 Aug 2024 15:57:34 +0800 Subject: [PATCH 58/67] CoapAuthManager added to relative handler --- .../coap/handler/CoapConnectHandler.java | 1 - .../coap/handler/CoapDisconnectHandler.java | 1 - .../coap/handler/CoapHeartbeatHandler.java | 1 - .../mqtt/ds/auth/CoapAuthManager.java | 10 +++++++--- .../coap/processor/CoapConnectProcessor.java | 9 +++++++-- .../ds/test/auth/TestCoapAuthManager.java | 20 +++++++++++++------ 6 files changed, 28 insertions(+), 14 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index e08b50990..22c422cef 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -41,7 +41,6 @@ public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMess if (coapMessage.getClientId() == null || coapMessage.getUserName() == null || coapMessage.getPassword() == null) { return false; } - // todo: check auth return true; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index f28971e44..57c9b32a1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -38,7 +38,6 @@ public class CoapDisconnectHandler implements CoapPacketHandler doAuth(String username, String password) { try { if (serviceConf.getUsername().equals(username) && PasswordHashUtil.validatePassword(password, hashedPassword, salt, algorithm, saltPosition)) { - return true; + return HookResult.newHookResult(HookResult.SUCCESS, null, null); } } catch (NoSuchAlgorithmException e) { logger.error("", e); } - return false; + return HookResult.newHookResult(HookResult.FAIL, MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USERNAME_OR_PASSWORD.byteValue(), Remark.AUTH_FAILED, null); } } diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java index cd93a767f..63963e429 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/upstream/coap/processor/CoapConnectProcessor.java @@ -18,16 +18,21 @@ import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.ds.auth.CoapAuthManager; import org.apache.rocketmq.mqtt.ds.upstream.coap.CoapUpstreamProcessor; import org.springframework.stereotype.Component; +import javax.annotation.Resource; import java.util.concurrent.CompletableFuture; @Component public class CoapConnectProcessor implements CoapUpstreamProcessor { + + @Resource + private CoapAuthManager coapAuthManager; + @Override public CompletableFuture process(CoapRequestMessage msg) { - // todo: RPC broadcast (store token) - return null; + return coapAuthManager.doAuth(msg.getUserName(), msg.getPassword()); } } diff --git a/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java b/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java index eb34202cd..c2899568b 100644 --- a/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java +++ b/mqtt-ds/src/test/java/org/apache/rocketmq/mqtt/ds/test/auth/TestCoapAuthManager.java @@ -17,6 +17,7 @@ package org.apache.rocketmq.mqtt.ds.test.auth; import org.apache.commons.lang.reflect.FieldUtils; +import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.ds.auth.CoapAuthManager; import org.apache.rocketmq.mqtt.ds.config.ServiceConf; import org.junit.Before; @@ -25,6 +26,9 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.when; import static org.junit.Assert.assertFalse; @@ -51,19 +55,23 @@ public void setUp() throws Exception { } @Test - public void doAuthWrongUsername() { + public void doAuthWrongUsername() throws ExecutionException, InterruptedException { String wrongUsername = "user222"; - assertFalse(coapAuthManager.doAuth(wrongUsername, secretKey)); + CompletableFuture future = coapAuthManager.doAuth(wrongUsername, secretKey); + assertFalse(future.get().isSuccess()); } @Test - public void doAuthWrongPassword() { + public void doAuthWrongPassword() throws ExecutionException, InterruptedException { String wrongPassword = "password222"; - assertFalse(coapAuthManager.doAuth(username, wrongPassword)); + CompletableFuture future = coapAuthManager.doAuth(username, wrongPassword); + assertFalse(future.get().isSuccess()); } @Test - public void doAuthSuccess() { - assertTrue(coapAuthManager.doAuth(username, secretKey)); + public void doAuthSuccess() throws ExecutionException, InterruptedException { + CompletableFuture future = coapAuthManager.doAuth(username, secretKey); + assertTrue(future.get().isSuccess()); } + } From 3aebc91f7cca5d24f40ebcc68c0168f74958b889 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Fri, 16 Aug 2024 09:40:44 +0800 Subject: [PATCH 59/67] Fix typo and code formatting. --- .../rocketmq/mqtt/common/util/PasswordHashUtil.java | 2 +- .../mqtt/cs/protocol/coap/handler/CoapConnectHandler.java | 6 +++++- .../cs/protocol/coap/handler/CoapDisconnectHandler.java | 6 +++++- .../cs/protocol/coap/handler/CoapHeartbeatHandler.java | 6 +++++- .../org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java | 8 ++++---- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java index 4bcd3ab7f..f001f9811 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/PasswordHashUtil.java @@ -53,7 +53,7 @@ public static String hashWithSalt(String password, String salt, String algorithm } - public static boolean validatePassword(String password, String storedHash, String salt, String algorithm, String saltPosition) throws NoSuchAlgorithmException{ + public static boolean validatePassword(String password, String storedHash, String salt, String algorithm, String saltPosition) throws NoSuchAlgorithmException { if (algorithm.equals("PLAIN")) { return password.equals(storedHash); } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index 22c422cef..053f2acb0 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -18,7 +18,11 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index 57c9b32a1..fefdb5f81 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -18,7 +18,11 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java index 636035c86..12a6d2001 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -18,7 +18,11 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; -import org.apache.rocketmq.mqtt.common.model.*; +import org.apache.rocketmq.mqtt.common.model.Constants; +import org.apache.rocketmq.mqtt.common.model.CoapMessage; +import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.model.CoapMessageType; +import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; diff --git a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java index 155737c90..d5c4b96c3 100644 --- a/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java +++ b/mqtt-ds/src/main/java/org/apache/rocketmq/mqtt/ds/auth/CoapAuthManager.java @@ -35,8 +35,8 @@ public class CoapAuthManager { public static Logger logger = LoggerFactory.getLogger(CoapAuthManager.class); private static String salt; - private static final String algorithm = "SHA-256"; - private static final String saltPosition = "suffix"; + private static final String ALGORITHM = "SHA-256"; + private static final String SALT_POSITION = "suffix"; private static String hashedPassword; @Resource @@ -45,12 +45,12 @@ public class CoapAuthManager { @PostConstruct public void init() throws NoSuchAlgorithmException { salt = PasswordHashUtil.generateSalt(16); - hashedPassword = PasswordHashUtil.hashWithSalt(serviceConf.getSecretKey(), salt, algorithm, saltPosition); + hashedPassword = PasswordHashUtil.hashWithSalt(serviceConf.getSecretKey(), salt, ALGORITHM, SALT_POSITION); } public CompletableFuture doAuth(String username, String password) { try { - if (serviceConf.getUsername().equals(username) && PasswordHashUtil.validatePassword(password, hashedPassword, salt, algorithm, saltPosition)) { + if (serviceConf.getUsername().equals(username) && PasswordHashUtil.validatePassword(password, hashedPassword, salt, ALGORITHM, SALT_POSITION)) { return HookResult.newHookResult(HookResult.SUCCESS, null, null); } } catch (NoSuchAlgorithmException e) { From 9a91b4ade09e999c9074004bb058f7c35f463dd6 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Mon, 19 Aug 2024 18:13:27 +0800 Subject: [PATCH 60/67] Change CoapTokenManager to CoapTokenUtil. Todo: modify other Handler and check authToken in connection mode; Move the exception handle in CoapTokenUtil; Remove CoapTokenManager. --- .../mqtt/common/util/CoapTokenUtil.java | 75 +++++++++++++++++++ .../common/test/util/TestCoapTokenUtil.java | 35 +++++++++ .../coap/handler/CoapConnectHandler.java | 37 +++++---- .../coap/handler/CoapDisconnectHandler.java | 12 +-- .../coap/handler/CoapHeartbeatHandler.java | 42 ++++++----- .../coap/handler/TestCoapConnectHandler.java | 26 +++---- .../handler/TestCoapDisconnectHandler.java | 23 ++---- .../handler/TestCoapHeartbeatHandler.java | 26 ++----- 8 files changed, 189 insertions(+), 87 deletions(-) create mode 100644 mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java create mode 100644 mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestCoapTokenUtil.java diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java new file mode 100644 index 000000000..348fb1b65 --- /dev/null +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java @@ -0,0 +1,75 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.util; + +import javax.crypto.Cipher; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +public class CoapTokenUtil { + + private static final String ALGORITHM = "AES"; + private static final String SECRET_KEY = "mySecretKey12345"; + private static final long EXPIRATION_TIME = 60000; + + public static String generateToken(String clientId) throws Exception { + long timestamp = System.currentTimeMillis(); + String data = clientId + ":" + timestamp; + return encrypt(data, SECRET_KEY); + } + + public static boolean isValid(String clientId, String token) { + try { + String decryptedData = decrypt(token, SECRET_KEY); + String[] parts = decryptedData.split(":"); + if (parts.length != 2) { + return false; + } + + String decryptedClientId = parts[0]; + long timestamp = Long.parseLong(parts[1]); + if (!decryptedClientId.equals(clientId)) { + return false; + } + long currentTime = System.currentTimeMillis(); + return currentTime - timestamp<= EXPIRATION_TIME; + + } catch (Exception e) { + return false; + } + } + + private static String encrypt(String data, String key) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, secretKey); + + byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); + return Base64.getEncoder().encodeToString(encryptedBytes); + } + + private static String decrypt(String token, String key) throws Exception { + Cipher cipher = Cipher.getInstance(ALGORITHM); + SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, secretKey); + + byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(token)); + return new String(decryptedBytes, StandardCharsets.UTF_8); + } +} diff --git a/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestCoapTokenUtil.java b/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestCoapTokenUtil.java new file mode 100644 index 000000000..3e7c8df57 --- /dev/null +++ b/mqtt-common/src/test/java/org/apache/rocketmq/mqtt/common/test/util/TestCoapTokenUtil.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.rocketmq.mqtt.common.test.util; + +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class TestCoapTokenUtil { + + @Test + public void test() throws Exception { + String clientId = "client123"; + String token = CoapTokenUtil.generateToken(clientId); + assertNotNull(token); + assertTrue(CoapTokenUtil.isValid(clientId, token)); + } + +} diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index 053f2acb0..e54e78300 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -23,9 +23,12 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -33,6 +36,7 @@ @Component public class CoapConnectHandler implements CoapPacketHandler { + private static Logger logger = LoggerFactory.getLogger(CoapConnectHandler.class); @Resource private CoapTokenManager coapTokenManager; @@ -65,19 +69,24 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, datagramChannelManager.writeResponse(response); return; } - // Create new token. - String authToken = coapTokenManager.createToken(coapMessage.getClientId()); - // Response success ack and return authToken. - CoapMessage response = new CoapMessage( - Constants.COAP_VERSION, - CoapMessageType.ACK, - coapMessage.getTokenLength(), - CoapMessageCode.CREATED, - coapMessage.getMessageId(), - coapMessage.getToken(), - authToken.getBytes(StandardCharsets.UTF_8), - coapMessage.getRemoteAddress() - ); - datagramChannelManager.writeResponse(response); + try { + // Create new token. + String authToken = CoapTokenUtil.generateToken(coapMessage.getClientId()); + // Response success ack and return authToken. + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.CREATED, + coapMessage.getMessageId(), + coapMessage.getToken(), + authToken.getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + } catch (Exception e) { + logger.error("", e); + } + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index fefdb5f81..860086c17 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -23,9 +23,11 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -33,9 +35,7 @@ @Component public class CoapDisconnectHandler implements CoapPacketHandler { - - @Resource - private CoapTokenManager coapTokenManager; + private static Logger logger = LoggerFactory.getLogger(CoapDisconnectHandler.class); @Resource private DatagramChannelManager datagramChannelManager; @@ -66,7 +66,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, return; } // Response unauthorized ack if authToken is not valid. - if (!coapTokenManager.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { + if (!CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, CoapMessageType.ACK, @@ -81,7 +81,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, return; } // Remove clientId-token from token manager. - coapTokenManager.removeToken(coapMessage.getClientId()); +// coapTokenManager.removeToken(coapMessage.getClientId()); // Response ack success CoapMessage response = new CoapMessage( Constants.COAP_VERSION, diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java index 12a6d2001..0ea674322 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapHeartbeatHandler.java @@ -23,9 +23,11 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import javax.annotation.Resource; @@ -33,9 +35,7 @@ @Component public class CoapHeartbeatHandler implements CoapPacketHandler { - - @Resource - private CoapTokenManager coapTokenManager; + private static Logger logger = LoggerFactory.getLogger(CoapHeartbeatHandler.class); @Resource private DatagramChannelManager datagramChannelManager; @@ -66,7 +66,7 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, return; } // Response unauthorized ack if authToken is not valid. - if (!coapTokenManager.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { + if (!CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { CoapMessage response = new CoapMessage( Constants.COAP_VERSION, CoapMessageType.ACK, @@ -80,19 +80,23 @@ public void doHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage, datagramChannelManager.writeResponse(response); return; } - // Refresh update time of token - coapTokenManager.refreshToken(coapMessage.getClientId()); - // Response ack success - CoapMessage response = new CoapMessage( - Constants.COAP_VERSION, - CoapMessageType.ACK, - coapMessage.getTokenLength(), - CoapMessageCode.CHANGED, - coapMessage.getMessageId(), - coapMessage.getToken(), - null, - coapMessage.getRemoteAddress() - ); - datagramChannelManager.writeResponse(response); + try { + // Refresh update time of token, and return new token + String newAuthToken = CoapTokenUtil.generateToken(coapMessage.getClientId()); + // Response ack success and new token + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + CoapMessageType.ACK, + coapMessage.getTokenLength(), + CoapMessageCode.CHANGED, + coapMessage.getMessageId(), + coapMessage.getToken(), + newAuthToken.getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + } catch (Exception e) { + logger.error("", e); + } } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java index ad79aa3ba..753d524af 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java @@ -24,6 +24,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapConnectHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; @@ -36,9 +37,7 @@ import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; @@ -51,9 +50,6 @@ public class TestCoapConnectHandler { private CoapConnectHandler coapConnectHandler; private CoapRequestMessage coapMessage; - @Mock - private CoapTokenManager coapTokenManager; - @Mock private DatagramChannelManager datagramChannelManager; @@ -63,7 +59,6 @@ public class TestCoapConnectHandler { @Before public void setUp() throws Exception { coapConnectHandler = new CoapConnectHandler(); - FieldUtils.writeDeclaredField(coapConnectHandler, "coapTokenManager", coapTokenManager, true); FieldUtils.writeDeclaredField(coapConnectHandler, "datagramChannelManager", datagramChannelManager, true); coapMessage = new CoapRequestMessage( Constants.COAP_VERSION, @@ -84,11 +79,11 @@ public void setUp() throws Exception { @Test public void testPreHandler() { assertTrue(coapConnectHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); coapMessage.setClientId(null); assertFalse(coapConnectHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test @@ -102,23 +97,24 @@ public void testConnectFail() { assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test public void testConnectSuccess() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - String authToken = "12345678"; - when(coapTokenManager.createToken(anyString())).thenReturn(authToken); coapConnectHandler.doHandler(ctx, coapMessage, successHookResult); - verify(coapTokenManager).createToken(coapMessage.getClientId()); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(CoapMessageCode.CREATED, response.getCode()); - assertEquals(authToken, new String(response.getPayload(), StandardCharsets.UTF_8)); + try { + assertTrue(CoapTokenUtil.isValid(coapMessage.getClientId(), new String(response.getPayload(), StandardCharsets.UTF_8))); + } catch (Exception e) { + throw new RuntimeException(e); + } return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } } \ No newline at end of file diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java index a7b5725b3..f22ec96bf 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java @@ -24,6 +24,7 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; @@ -52,9 +53,6 @@ public class TestCoapDisconnectHandler { private CoapDisconnectHandler coapDisconnectHandler; private CoapRequestMessage coapMessage; - @Mock - private CoapTokenManager coapTokenManager; - @Mock private DatagramChannelManager datagramChannelManager; @@ -64,7 +62,6 @@ public class TestCoapDisconnectHandler { @Before public void setUp() throws Exception { coapDisconnectHandler = new CoapDisconnectHandler(); - FieldUtils.writeDeclaredField(coapDisconnectHandler, "coapTokenManager", coapTokenManager, true); FieldUtils.writeDeclaredField(coapDisconnectHandler, "datagramChannelManager", datagramChannelManager, true); coapMessage = new CoapRequestMessage( Constants.COAP_VERSION, @@ -78,17 +75,17 @@ public void setUp() throws Exception { ); coapMessage.setRequestType(CoapRequestType.DISCONNECT); coapMessage.setClientId("123"); - coapMessage.setAuthToken("12345678"); + coapMessage.setAuthToken(CoapTokenUtil.generateToken("123")); } @Test public void testPreHandler() { assertTrue(coapDisconnectHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); coapMessage.setClientId(null); assertFalse(coapDisconnectHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test @@ -102,38 +99,34 @@ public void testDisconnectFail() { assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test public void testDisconnectUnauthorized() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(false); + coapMessage.setAuthToken("wrongToken"); coapDisconnectHandler.doHandler(ctx, coapMessage, successHookResult); - verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(CoapMessageCode.UNAUTHORIZED, response.getCode()); assertEquals("AuthToken is not valid.", new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test public void testDisconnectSuccess() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(true); coapDisconnectHandler.doHandler(ctx, coapMessage, successHookResult); - verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); - verify(coapTokenManager).removeToken(coapMessage.getClientId()); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(CoapMessageCode.DELETED, response.getCode()); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java index fe442f071..a05ae052b 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java @@ -24,9 +24,9 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapRequestType; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapHeartbeatHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,11 +39,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestCoapHeartbeatHandler { @@ -51,9 +49,6 @@ public class TestCoapHeartbeatHandler { private CoapHeartbeatHandler coapHeartbeatHandler; private CoapRequestMessage coapMessage; - @Mock - private CoapTokenManager coapTokenManager; - @Mock private DatagramChannelManager datagramChannelManager; @@ -63,7 +58,6 @@ public class TestCoapHeartbeatHandler { @Before public void setUp() throws Exception { coapHeartbeatHandler = new CoapHeartbeatHandler(); - FieldUtils.writeDeclaredField(coapHeartbeatHandler, "coapTokenManager", coapTokenManager, true); FieldUtils.writeDeclaredField(coapHeartbeatHandler, "datagramChannelManager", datagramChannelManager, true); coapMessage = new CoapRequestMessage( Constants.COAP_VERSION, @@ -77,17 +71,17 @@ public void setUp() throws Exception { ); coapMessage.setRequestType(CoapRequestType.HEARTBEAT); coapMessage.setClientId("123"); - coapMessage.setAuthToken("12345678"); + coapMessage.setAuthToken(CoapTokenUtil.generateToken("123")); } @Test public void testPreHandler() { assertTrue(coapHeartbeatHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); coapMessage.setClientId(null); assertFalse(coapHeartbeatHandler.preHandler(ctx, coapMessage)); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test @@ -101,38 +95,34 @@ public void testHeartbeatFail() { assertEquals(failHookResult.getRemark(), new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test public void testHeartbeatUnauthorized() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(false); + coapMessage.setAuthToken("wrongToken"); coapHeartbeatHandler.doHandler(ctx, coapMessage, successHookResult); - verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(CoapMessageCode.UNAUTHORIZED, response.getCode()); assertEquals("AuthToken is not valid.", new String(response.getPayload(), StandardCharsets.UTF_8)); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } @Test public void testHeartbeatSuccess() { HookResult successHookResult = new HookResult(HookResult.SUCCESS, null, null); - when(coapTokenManager.isValid(anyString(), anyString())).thenReturn(true); coapHeartbeatHandler.doHandler(ctx, coapMessage, successHookResult); - verify(coapTokenManager).isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); - verify(coapTokenManager).refreshToken(coapMessage.getClientId()); verify(datagramChannelManager).writeResponse(argThat(response -> { assertEquals(CoapMessageCode.CHANGED, response.getCode()); return true; })); - verifyNoMoreInteractions(coapTokenManager, datagramChannelManager, ctx); + verifyNoMoreInteractions(datagramChannelManager, ctx); } } From 7f2f69e3f04b77b5fe0bc187eacd7f3d7592255f Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 20 Aug 2024 09:49:27 +0800 Subject: [PATCH 61/67] modify other Handler and check authToken in connection mode. --- .../rocketmq/mqtt/cs/config/ConnectConf.java | 10 ++++++++++ .../cs/protocol/coap/handler/CoapAckHandler.java | 8 ++++++++ .../protocol/coap/handler/CoapPublishHandler.java | 9 ++++++++- .../coap/handler/CoapSubscribeHandler.java | 9 ++++++++- .../protocol/coap/handler/TestCoapAckHandler.java | 12 ++++++++++-- .../coap/handler/TestCoapPublishHandler.java | 11 +++++++++-- .../coap/handler/TestCoapSubscribeHandler.java | 14 ++++++++++---- 7 files changed, 63 insertions(+), 10 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java index b20858ec6..8921b4870 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/config/ConnectConf.java @@ -54,6 +54,8 @@ public class ConnectConf { private boolean enableQuic = false; + private boolean enableCoapConnect = false; + private boolean enableTlsSever = false; private boolean needClientAuth = false; private String sslCaCertFile; @@ -374,4 +376,12 @@ public long getCoapSessionTimeout() { public void setCoapSessionTimeout(long coapSessionTimeout) { this.coapSessionTimeout = coapSessionTimeout; } + + public boolean isEnableCoapConnect() { + return enableCoapConnect; + } + + public void setEnableCoapConnect(boolean enableCoapConnect) { + this.enableCoapConnect = enableCoapConnect; + } } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java index d3d5f5ed1..56f264b51 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -19,6 +19,8 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; +import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.springframework.stereotype.Component; @@ -31,8 +33,14 @@ public class CoapAckHandler implements CoapPacketHandler { @Resource private CoapRetryManager coapRetryManager; + @Resource + private ConnectConf connectConf; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { + if (connectConf.isEnableCoapConnect()) { + return (coapMessage.getClientId() != null) && (coapMessage.getAuthToken() != null) && CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + } return true; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java index 8fedb4198..0e3b856fa 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapPublishHandler.java @@ -23,7 +23,9 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.springframework.stereotype.Component; @@ -35,9 +37,14 @@ public class CoapPublishHandler implements CoapPacketHandler @Resource private DatagramChannelManager datagramChannelManager; + @Resource + private ConnectConf connectConf; + @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { - // todo: check token if connection mode + if (connectConf.isEnableCoapConnect()) { + return (coapMessage.getClientId() != null) && (coapMessage.getAuthToken() != null) && CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + } return true; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 3f86d41a1..49fed2e06 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -27,8 +27,10 @@ import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; +import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.common.util.TopicUtils; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; +import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.CoapSession; import org.apache.rocketmq.mqtt.cs.session.loop.CoapSessionLoop; @@ -56,11 +58,16 @@ public class CoapSubscribeHandler implements CoapPacketHandler Date: Tue, 20 Aug 2024 09:55:28 +0800 Subject: [PATCH 62/67] Remove CoapTokenManager. --- .../coap/handler/CoapConnectHandler.java | 4 - .../mqtt/cs/session/CoapTokenManager.java | 104 ------------------ .../coap/handler/TestCoapConnectHandler.java | 3 - .../handler/TestCoapDisconnectHandler.java | 3 - .../cs/test/session/TestCoapTokenManager.java | 73 ------------ 5 files changed, 187 deletions(-) delete mode 100644 mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java delete mode 100644 mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index e54e78300..43ae5f81e 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -26,7 +26,6 @@ import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; @@ -38,9 +37,6 @@ public class CoapConnectHandler implements CoapPacketHandler { private static Logger logger = LoggerFactory.getLogger(CoapConnectHandler.class); - @Resource - private CoapTokenManager coapTokenManager; - @Resource private DatagramChannelManager datagramChannelManager; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java deleted file mode 100644 index db4e602c1..000000000 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/session/CoapTokenManager.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.session; - -import org.apache.rocketmq.common.ThreadFactoryImpl; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -@Component -public class CoapTokenManager { - - private ScheduledThreadPoolExecutor scheduler; - - private ConcurrentMap tokenMap = new ConcurrentHashMap<>(1024); - - private static final int SCHEDULE_INTERVAL = 1000; - private static final long TIMEOUT = 3000; - - @PostConstruct - public void init() { - scheduler = new ScheduledThreadPoolExecutor(1, new ThreadFactoryImpl("coap_token_alive_thread_")); - scheduler.scheduleWithFixedDelay(this::clearExpiredToken, SCHEDULE_INTERVAL, SCHEDULE_INTERVAL, TimeUnit.MILLISECONDS); - } - - public String createToken(String clientId) { - CoapToken coapToken = new CoapToken(clientId); - tokenMap.put(clientId, coapToken); - return coapToken.token; - } - - public void removeToken(String clientId) { - tokenMap.remove(clientId); - } - - public String getToken(String clientId) { - CoapToken coapToken = tokenMap.get(clientId); - return coapToken == null ? null : coapToken.token; - } - - public boolean isValid(String clientId, String token) { - CoapToken coapToken = tokenMap.get(clientId); - if (coapToken == null) { - return false; - } - return coapToken.token.equals(token); - } - - public void refreshToken(String clientId) { - CoapToken coapToken = tokenMap.get(clientId); - if (coapToken != null) { - coapToken.lastUpdateTime = System.currentTimeMillis(); - } - } - - public ConcurrentMap getTokenMap() { - return tokenMap; - } - - public void setTokenMap(ConcurrentMap tokenMap) { - this.tokenMap = tokenMap; - } - - private void clearExpiredToken() { - if (tokenMap.isEmpty()) { - return; - } - for (CoapToken token : tokenMap.values()) { - if (System.currentTimeMillis() - token.lastUpdateTime > TIMEOUT) { - tokenMap.remove(token.clientId); - } - } - } - - public class CoapToken { - private String clientId; - private String token = UUID.randomUUID().toString().replace("-", "").substring(0, 10); - private long lastUpdateTime = System.currentTimeMillis(); - - public CoapToken(String clientId) { - this.clientId = clientId; - } - } - -} diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java index 753d524af..ed40b45d5 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapConnectHandler.java @@ -27,7 +27,6 @@ import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapConnectHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -38,11 +37,9 @@ import java.nio.charset.StandardCharsets; import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) public class TestCoapConnectHandler { diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java index f22ec96bf..52a3d67f6 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java @@ -27,7 +27,6 @@ import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; import org.apache.rocketmq.mqtt.cs.channel.DatagramChannelManager; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapDisconnectHandler; -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,11 +39,9 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; @RunWith(MockitoJUnitRunner.class) diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java deleted file mode 100644 index 82fadaf3c..000000000 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/session/TestCoapTokenManager.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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.apache.rocketmq.mqtt.cs.test.session; - -import org.apache.rocketmq.mqtt.cs.session.CoapTokenManager; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.junit.MockitoJUnitRunner; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; - -@RunWith(MockitoJUnitRunner.class) -public class TestCoapTokenManager { - - private CoapTokenManager coapTokenManager; - - @Before - public void setUp() { - coapTokenManager = new CoapTokenManager(); - } - - @Test - public void testCreateToken() { - String clientId = "test111"; - String token = coapTokenManager.createToken(clientId); - - assertEquals(1, coapTokenManager.getTokenMap().size()); - assertEquals(token, coapTokenManager.getToken(clientId)); - } - - @Test - public void testRemoveToken() { - String clientId = "test111"; - - String token = coapTokenManager.createToken(clientId); - assertEquals(1, coapTokenManager.getTokenMap().size()); - - coapTokenManager.removeToken(clientId); - assertEquals(0, coapTokenManager.getTokenMap().size()); - } - - @Test - public void testIsValid() { - String clientId = "test111"; - String token = coapTokenManager.createToken(clientId); - assertTrue(coapTokenManager.isValid(clientId, token)); - } - - @Test - public void testNotValid() { - String clientId = "test111"; - String token = coapTokenManager.createToken(clientId); - assertFalse(coapTokenManager.isValid(clientId, "wrongToken")); - } - -} From 4b75813458af7135c28676de0f0d955625ca8fef Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 20 Aug 2024 13:47:58 +0800 Subject: [PATCH 63/67] fix typo --- .../org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java index 348fb1b65..b17a5861c 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java @@ -48,7 +48,7 @@ public static boolean isValid(String clientId, String token) { return false; } long currentTime = System.currentTimeMillis(); - return currentTime - timestamp<= EXPIRATION_TIME; + return currentTime - timestamp <= EXPIRATION_TIME; } catch (Exception e) { return false; From d812bb586048d9535098203f6f0f2119cd450399 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 20 Aug 2024 15:10:32 +0800 Subject: [PATCH 64/67] change base64 decoder to urlDecoder. --- .../org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java index b17a5861c..f41508067 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/util/CoapTokenUtil.java @@ -26,7 +26,7 @@ public class CoapTokenUtil { private static final String ALGORITHM = "AES"; private static final String SECRET_KEY = "mySecretKey12345"; - private static final long EXPIRATION_TIME = 60000; + private static final long EXPIRATION_TIME = 600000; public static String generateToken(String clientId) throws Exception { long timestamp = System.currentTimeMillis(); @@ -61,7 +61,7 @@ private static String encrypt(String data, String key) throws Exception { cipher.init(Cipher.ENCRYPT_MODE, secretKey); byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8)); - return Base64.getEncoder().encodeToString(encryptedBytes); + return Base64.getUrlEncoder().withoutPadding().encodeToString(encryptedBytes); } private static String decrypt(String token, String key) throws Exception { @@ -69,7 +69,7 @@ private static String decrypt(String token, String key) throws Exception { SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, secretKey); - byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(token)); + byte[] decryptedBytes = cipher.doFinal(Base64.getUrlDecoder().decode(token)); return new String(decryptedBytes, StandardCharsets.UTF_8); } } From 4c0a9338a434ade8b9eb0fe12e24fe609104cde9 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 20 Aug 2024 15:11:11 +0800 Subject: [PATCH 65/67] fix bug: CoapDecoder missing authToken. --- .../java/org/apache/rocketmq/mqtt/common/model/Constants.java | 1 + .../org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java | 2 ++ 2 files changed, 3 insertions(+) diff --git a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java index 9b5d83037..12e23ab4d 100644 --- a/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java +++ b/mqtt-common/src/main/java/org/apache/rocketmq/mqtt/common/model/Constants.java @@ -87,4 +87,5 @@ public class Constants { public static final String COAP_QUERY_EXPIRY = "expiry"; public static final String COAP_QUERY_USER_NAME = "username"; public static final String COAP_QUERY_PASSWORD = "password"; + public static final String COAP_AUTH_TOKEN = "token"; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java index 5d61213e1..803e90794 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/CoapDecoder.java @@ -217,6 +217,8 @@ public void decode(ChannelHandlerContext ctx, DatagramPacket packet, List Date: Tue, 20 Aug 2024 15:17:05 +0800 Subject: [PATCH 66/67] fix bug: should not add auth check in CoapAckHandler. --- .../mqtt/cs/protocol/coap/handler/CoapAckHandler.java | 8 -------- .../cs/test/protocol/coap/handler/TestCoapAckHandler.java | 8 +------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java index 56f264b51..d3d5f5ed1 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapAckHandler.java @@ -19,8 +19,6 @@ import io.netty.channel.ChannelHandlerContext; import org.apache.rocketmq.mqtt.common.hook.HookResult; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; -import org.apache.rocketmq.mqtt.common.util.CoapTokenUtil; -import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.CoapPacketHandler; import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.springframework.stereotype.Component; @@ -33,14 +31,8 @@ public class CoapAckHandler implements CoapPacketHandler { @Resource private CoapRetryManager coapRetryManager; - @Resource - private ConnectConf connectConf; - @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { - if (connectConf.isEnableCoapConnect()) { - return (coapMessage.getClientId() != null) && (coapMessage.getAuthToken() != null) && CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); - } return true; } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java index e8b7498e9..48a706a69 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapAckHandler.java @@ -22,7 +22,6 @@ import org.apache.rocketmq.mqtt.common.model.CoapMessageCode; import org.apache.rocketmq.mqtt.common.model.CoapMessageType; import org.apache.rocketmq.mqtt.common.model.CoapRequestMessage; -import org.apache.rocketmq.mqtt.cs.config.ConnectConf; import org.apache.rocketmq.mqtt.cs.protocol.coap.handler.CoapAckHandler; import org.apache.rocketmq.mqtt.cs.session.infly.CoapRetryManager; import org.junit.Before; @@ -50,16 +49,12 @@ public class TestCoapAckHandler { @Mock private ChannelHandlerContext ctx; - @Mock - private ConnectConf connectConf; - private CoapRequestMessage coapMessage; @Before public void setUp() throws IllegalAccessException { coapAckHandler = new CoapAckHandler(); FieldUtils.writeDeclaredField(coapAckHandler, "coapRetryManager", coapRetryManager, true); - FieldUtils.writeDeclaredField(coapAckHandler, "connectConf", connectConf, true); coapMessage = new CoapRequestMessage( 1, CoapMessageType.ACK, @@ -74,7 +69,6 @@ public void setUp() throws IllegalAccessException { @Test public void testPreHandler() { - when(connectConf.isEnableCoapConnect()).thenReturn(false); boolean result = coapAckHandler.preHandler(ctx, coapMessage); assertTrue(result); } @@ -88,6 +82,6 @@ public void testAckSuccess() { verify(coapRetryManager).contains(anyInt()); verify(coapRetryManager).ackMessage(anyInt()); - verifyNoMoreInteractions(ctx, coapRetryManager, connectConf); + verifyNoMoreInteractions(ctx, coapRetryManager); } } From 8aafe25d08f7a89f305d7794d5a6724227964d99 Mon Sep 17 00:00:00 2001 From: Woguagua <22221132@zju.edu.cn> Date: Tue, 20 Aug 2024 15:30:27 +0800 Subject: [PATCH 67/67] Add response for unauthorized request in connection mode. --- .../coap/handler/CoapConnectHandler.java | 11 +++++++ .../coap/handler/CoapDisconnectHandler.java | 11 +++++++ .../coap/handler/CoapHeartbeatHandler.java | 11 +++++++ .../coap/handler/CoapPublishHandler.java | 30 ++++++++++++++++++- .../coap/handler/CoapSubscribeHandler.java | 29 +++++++++++++++++- .../coap/handler/TestCoapConnectHandler.java | 4 +++ .../handler/TestCoapDisconnectHandler.java | 4 +++ .../handler/TestCoapHeartbeatHandler.java | 4 +++ 8 files changed, 102 insertions(+), 2 deletions(-) diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java index 43ae5f81e..6d4eff546 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapConnectHandler.java @@ -43,6 +43,17 @@ public class CoapConnectHandler implements CoapPacketHandler @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { if (coapMessage.getClientId() == null || coapMessage.getUserName() == null || coapMessage.getPassword() == null) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.BAD_REQUEST, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Not complete info for connection mode.".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); return false; } return true; diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java index 860086c17..cdb6c711f 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapDisconnectHandler.java @@ -43,6 +43,17 @@ public class CoapDisconnectHandler implements CoapPacketHandler { @@ -43,7 +44,34 @@ public class CoapPublishHandler implements CoapPacketHandler @Override public boolean preHandler(ChannelHandlerContext ctx, CoapRequestMessage coapMessage) { if (connectConf.isEnableCoapConnect()) { - return (coapMessage.getClientId() != null) && (coapMessage.getAuthToken() != null) && CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken()); + if (coapMessage.getClientId() == null || coapMessage.getAuthToken() == null) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.BAD_REQUEST, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Not complete info for connection mode.".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return false; + } + if (!CoapTokenUtil.isValid(coapMessage.getClientId(), coapMessage.getAuthToken())) { + CoapMessage response = new CoapMessage( + Constants.COAP_VERSION, + coapMessage.getType() == CoapMessageType.CON ? CoapMessageType.ACK : CoapMessageType.NON, + coapMessage.getTokenLength(), + CoapMessageCode.UNAUTHORIZED, + coapMessage.getMessageId(), + coapMessage.getToken(), + "Invalid authToken.".getBytes(StandardCharsets.UTF_8), + coapMessage.getRemoteAddress() + ); + datagramChannelManager.writeResponse(response); + return false; + } } return true; } diff --git a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java index 49fed2e06..c030928c9 100644 --- a/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java +++ b/mqtt-cs/src/main/java/org/apache/rocketmq/mqtt/cs/protocol/coap/handler/CoapSubscribeHandler.java @@ -66,7 +66,34 @@ public class CoapSubscribeHandler implements CoapPacketHandler { + assertEquals(CoapMessageCode.BAD_REQUEST, response.getCode()); + return true; + })); verifyNoMoreInteractions(datagramChannelManager, ctx); } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java index 52a3d67f6..4eb088191 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapDisconnectHandler.java @@ -82,6 +82,10 @@ public void testPreHandler() { coapMessage.setClientId(null); assertFalse(coapDisconnectHandler.preHandler(ctx, coapMessage)); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.BAD_REQUEST, response.getCode()); + return true; + })); verifyNoMoreInteractions(datagramChannelManager, ctx); } diff --git a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java index a05ae052b..9321d814a 100644 --- a/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java +++ b/mqtt-cs/src/test/java/org/apache/rocketmq/mqtt/cs/test/protocol/coap/handler/TestCoapHeartbeatHandler.java @@ -81,6 +81,10 @@ public void testPreHandler() { coapMessage.setClientId(null); assertFalse(coapHeartbeatHandler.preHandler(ctx, coapMessage)); + verify(datagramChannelManager).writeResponse(argThat(response -> { + assertEquals(CoapMessageCode.BAD_REQUEST, response.getCode()); + return true; + })); verifyNoMoreInteractions(datagramChannelManager, ctx); }