This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 18a220d481 optimize : support multi-version codec & fix not returning
client registration failure msg (#7000)
18a220d481 is described below
commit 18a220d481265ad7b95d425406bc1ba9f7722d88
Author: justabug <[email protected]>
AuthorDate: Sat Feb 7 23:49:23 2026 +0800
optimize : support multi-version codec & fix not returning client
registration failure msg (#7000)
---
changes/en-us/2.x.md | 1 +
changes/zh-cn/2.x.md | 1 +
.../resources/static/console-fe/package-lock.json | 25 +-
.../seata/core/protocol/ProtocolConstants.java | 9 +-
.../seata/core/protocol/RegisterRMResponse.java | 1 +
.../seata/core/protocol/RegisterTMResponse.java | 1 +
.../org/apache/seata/core/protocol/Version.java | 3 +
.../seata/core/rpc/netty/MultiProtocolDecoder.java | 71 +++-
.../seata/core/rpc/netty/NettyClientBootstrap.java | 14 +-
.../seata/core/rpc/netty/ProtocolDecoder.java | 2 +
.../seata/core/rpc/netty/ProtocolEncoder.java | 2 +
.../seata/core/rpc/netty/v0/ProtocolDecoderV0.java | 7 +-
.../seata/core/rpc/netty/v0/ProtocolEncoderV0.java | 8 +-
.../seata/core/rpc/netty/v1/ProtocolDecoderV1.java | 7 +-
.../seata/core/rpc/netty/v1/ProtocolEncoderV1.java | 9 +-
.../ProtocolDecoderV2.java} | 16 +-
.../ProtocolEncoderV2.java} | 17 +-
.../core/rpc/processor/server/RegRmProcessor.java | 1 +
.../core/rpc/processor/server/RegTmProcessor.java | 1 +
.../seata/core/protocol/ProtocolConstantsTest.java | 2 +-
.../processor/MockRegisterProcessor.java | 46 ++-
.../package-lock.json | 16 +-
.../serializer/seata/MessageCodecFactory.java | 24 +-
.../seata/serializer/seata/MultiVersionCodec.java | 47 +--
.../seata/serializer/seata/SeataSerializer.java | 5 +-
.../v2/AbstractIdentifyResponseCodecV2.java | 69 ++++
.../protocol/v2/RegisterRMResponseCodecV2.java | 17 +-
.../protocol/v2/RegisterTMResponseCodecV2.java | 17 +-
.../seata/serializer/SeataSerializerV1.java | 89 +++++
.../seata/serializer/SeataSerializerV2.java | 41 +--
.../serializer/seata/MultiVersionCodecTest.java | 39 +-
.../protocol/v2/RegisterResponseCodecV2Test.java | 89 +++++
.../seata/serializer/SeataSerializerV1Test.java | 82 +++++
.../core/rpc/netty/CodecTestCheckAuthHandler.java | 21 +-
.../core/rpc/netty/MultiProtocolDecoderTest.java | 148 ++++++++
.../seata/core/rpc/netty/TestClientHandler.java | 73 ++++
.../seata/core/rpc/netty/TestServerHandler.java | 138 +++++++
.../MultiVersionCompatibilityTest.java | 405 +++++++++++++++++++++
.../netty/multiversion/ServerV1ToClientV1Test.java | 70 ++++
.../netty/multiversion/ServerV1ToClientV2Test.java | 71 ++++
.../netty/multiversion/ServerV2ToClientV1Test.java | 68 ++++
.../netty/multiversion/ServerV2ToClientV2Test.java | 72 ++++
....apache.seata.core.rpc.RegisterCheckAuthHandler | 17 +
43 files changed, 1670 insertions(+), 192 deletions(-)
diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 6dc97d36a1..0cfbd82a5f 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -21,6 +21,7 @@ Add changes here for all PR submitted to the 2.x branch.
### feature:
- [[#7882](https://github.com/apache/incubator-seata/pull/7882)] add metrics
for NamingServer
- [[#7760](https://github.com/apache/incubator-seata/pull/7760)] unify
Jackson/fastjson serialization
+- [[#7000](https://github.com/apache/incubator-seata/pull/7000)] support
multi-version codec & fix not returning client registration failure msg.
- [[#7865](https://github.com/apache/incubator-seata/pull/7865)] add Benchmark
CLI tool
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index d988bdf052..7c00403684 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -22,6 +22,7 @@
- [[#7882](https://github.com/apache/incubator-seata/pull/7882)]
为NamingServer增加Metrics监控
- [[#7760](https://github.com/apache/incubator-seata/pull/7760)]
统一Jackson/fastjson序列化器
+- [[#7000](https://github.com/apache/incubator-seata/pull/7000)]
支持多版本codec,修复不返回客户端注册失败消息的问题
- [[#7865](https://github.com/apache/incubator-seata/pull/7865)] 新增 Benchmark
命令行工具
### bugfix:
diff --git a/console/src/main/resources/static/console-fe/package-lock.json
b/console/src/main/resources/static/console-fe/package-lock.json
index 426f76b835..359fba8672 100644
--- a/console/src/main/resources/static/console-fe/package-lock.json
+++ b/console/src/main/resources/static/console-fe/package-lock.json
@@ -7706,16 +7706,6 @@
"node": ">= 0.8"
}
},
- "node_modules/encoding": {
- "version": "0.1.13",
- "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
- "integrity":
"sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "iconv-lite": "^0.6.2"
- }
- },
"node_modules/enhanced-resolve": {
"version": "5.18.3",
"resolved":
"https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz",
@@ -10240,19 +10230,6 @@
"node": ">=10.18"
}
},
- "node_modules/iconv-lite": {
- "version": "0.6.3",
- "resolved":
"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
- "integrity":
"sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "safer-buffer": ">= 2.1.2 < 3.0.0"
- },
- "engines": {
- "node": ">=0.10.0"
- }
- },
"node_modules/icss-utils": {
"version": "5.1.0",
"resolved":
"https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz",
@@ -14998,7 +14975,7 @@
"version": "2.1.2",
"resolved":
"https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity":
"sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
- "devOptional": true,
+ "dev": true,
"license": "MIT"
},
"node_modules/sass-graph": {
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
b/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
index c51cbb936b..1d527e0e8e 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/ProtocolConstants.java
@@ -38,14 +38,19 @@ public interface ProtocolConstants {
byte VERSION_0 = 0;
/**
- * Protocol version
+ * Protocol version 1
*/
byte VERSION_1 = 1;
+ /**
+ * Protocol version 2
+ */
+ byte VERSION_2 = 2;
+
/**
* Protocol version
*/
- byte VERSION = VERSION_1;
+ byte VERSION = VERSION_2;
/**
* Max frame length
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
b/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
index 7dbce406cd..c068c38835 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
@@ -39,6 +39,7 @@ public class RegisterRMResponse extends
AbstractIdentifyResponse implements Seri
public RegisterRMResponse(boolean result) {
super();
setIdentified(result);
+ setResultCode(result ? ResultCode.Success : ResultCode.Failed);
}
@Override
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
b/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
index 0158a9b83e..c5ae0f1060 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/RegisterTMResponse.java
@@ -39,6 +39,7 @@ public class RegisterTMResponse extends
AbstractIdentifyResponse implements Seri
public RegisterTMResponse(boolean result) {
super();
setIdentified(result);
+ setResultCode(result ? ResultCode.Success : ResultCode.Failed);
}
@Override
diff --git a/core/src/main/java/org/apache/seata/core/protocol/Version.java
b/core/src/main/java/org/apache/seata/core/protocol/Version.java
index 3c7e77376c..219e6b8f6a 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/Version.java
+++ b/core/src/main/java/org/apache/seata/core/protocol/Version.java
@@ -41,6 +41,9 @@ public class Version {
private static final String VERSION_0_7_1 = "0.7.1";
private static final String VERSION_1_5_0 = "1.5.0";
private static final String VERSION_2_3_0 = "2.3.0";
+
+ public static final String VERSION_0_7_0 = "0.7.0";
+
private static final int MAX_VERSION_DOT = 3;
/**
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
index feef1e6f51..8d19e29c1a 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoder.java
@@ -27,9 +27,13 @@ import org.apache.seata.core.rpc.netty.v0.ProtocolDecoderV0;
import org.apache.seata.core.rpc.netty.v0.ProtocolEncoderV0;
import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolDecoderV2;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
/**
@@ -64,17 +68,27 @@ public class MultiProtocolDecoder extends
LengthFieldBasedFrameDecoder {
private final ChannelHandler[] channelHandlers;
+ private final byte maxCurrentVersion; // For testing purposes
+
public MultiProtocolDecoder(ChannelHandler... channelHandlers) {
// default is 8M
this(ProtocolConstants.MAX_FRAME_LENGTH, channelHandlers);
}
- public MultiProtocolDecoder() {
- // default is 8M
- this(ProtocolConstants.MAX_FRAME_LENGTH, null);
+ /**
+ * Constructor for testing purposes to force a specific protocol version
+ * @param maxCurrentVersion the protocol version to force
+ * @param channelHandlers additional channel handlers
+ */
+ MultiProtocolDecoder(byte maxCurrentVersion, ChannelHandler...
channelHandlers) {
+ this(ProtocolConstants.MAX_FRAME_LENGTH, maxCurrentVersion,
channelHandlers);
}
public MultiProtocolDecoder(int maxFrameLength, ChannelHandler[]
channelHandlers) {
+ this(maxFrameLength, ProtocolConstants.VERSION, channelHandlers);
+ }
+
+ MultiProtocolDecoder(int maxFrameLength, byte maxCurrentVersion,
ChannelHandler[] channelHandlers) {
/*
int maxFrameLength,
int lengthFieldOffset, magic code is 2B, and version is 1B, and then
FullLength. so value is 3
@@ -83,13 +97,16 @@ public class MultiProtocolDecoder extends
LengthFieldBasedFrameDecoder {
int initialBytesToStrip we will check magic code and version self, so
do not strip any bytes. so values is 0
*/
super(maxFrameLength, 3, 4, -7, 0);
+ this.maxCurrentVersion = maxCurrentVersion;
this.protocolDecoderMap = ImmutableMap.<Byte, ProtocolDecoder>builder()
.put(ProtocolConstants.VERSION_0, new ProtocolDecoderV0())
.put(ProtocolConstants.VERSION_1, new ProtocolDecoderV1())
+ .put(ProtocolConstants.VERSION_2, new ProtocolDecoderV2())
.build();
this.protocolEncoderMap = ImmutableMap.<Byte, ProtocolEncoder>builder()
.put(ProtocolConstants.VERSION_0, new ProtocolEncoderV0())
.put(ProtocolConstants.VERSION_1, new ProtocolEncoderV1())
+ .put(ProtocolConstants.VERSION_2, new ProtocolEncoderV2())
.build();
this.channelHandlers = channelHandlers;
}
@@ -110,22 +127,17 @@ public class MultiProtocolDecoder extends
LengthFieldBasedFrameDecoder {
if (decoded instanceof ByteBuf) {
frame = (ByteBuf) decoded;
- ProtocolDecoder decoder = protocolDecoderMap.get(version);
- if (decoder == null) {
+ // Ensure version is within supported range
+ if (version > maxCurrentVersion) {
+ version = maxCurrentVersion;
LOGGER.error(
- "Decoder not found, version={}, use current
version({})",
+ "Detected version {} is greater than max supported
version {}, using max supported version.",
version,
- ProtocolConstants.VERSION);
- decoder =
protocolDecoderMap.get(ProtocolConstants.VERSION);
+ maxCurrentVersion);
}
+ ProtocolDecoder decoder = protocolDecoderMap.get(version);
ProtocolEncoder encoder = protocolEncoderMap.get(version);
- if (encoder == null) {
- LOGGER.error(
- "Encoder not found, version: {}, use current
version({})",
- version,
- ProtocolConstants.VERSION);
- encoder =
protocolEncoderMap.get(ProtocolConstants.VERSION);
- }
+
try {
if (decoder == null || encoder == null) {
throw new UnsupportedOperationException("Unsupported
version: " + version);
@@ -135,6 +147,8 @@ public class MultiProtocolDecoder extends
LengthFieldBasedFrameDecoder {
if (version != ProtocolConstants.VERSION_0) {
frame.release();
}
+ // Remove existing encoder if it exists (for client-side
compatibility)
+ removeExistingEncoder(ctx, encoder);
ctx.pipeline().addLast((ChannelHandler) decoder);
ctx.pipeline().addLast((ChannelHandler) encoder);
if (channelHandlers != null) {
@@ -186,4 +200,31 @@ public class MultiProtocolDecoder extends
LengthFieldBasedFrameDecoder {
protected boolean isV0(byte version) {
return version == ProtocolConstants.VERSION_0;
}
+
+ /**
+ * Remove existing encoder from pipeline to avoid conflicts when adding
new encoder.
+ * This is particularly important for client-side where an encoder may
already exist.
+ */
+ private void removeExistingEncoder(ChannelHandlerContext ctx,
ProtocolEncoder newEncoder) {
+ // Create a list to collect handlers to remove (avoid
ConcurrentModificationException)
+ List<String> handlersToRemove = new ArrayList<>();
+
+ ctx.pipeline().toMap().forEach((name, handler) -> {
+ // Remove if it's a ProtocolEncoder but not the same instance
we're about to add
+ // and not a ProtocolDecoder (which might also implement
ProtocolEncoder)
+ if (handler instanceof ProtocolEncoder && !(handler instanceof
ProtocolDecoder) && handler != newEncoder) {
+ handlersToRemove.add(name);
+ }
+ });
+
+ // Remove the handlers
+ handlersToRemove.forEach(name -> {
+ try {
+ ctx.pipeline().remove(name);
+ LOGGER.debug("Removed existing encoder: {}", name);
+ } catch (Exception e) {
+ LOGGER.warn("Failed to remove existing encoder {}: {}", name,
e.getMessage());
+ }
+ });
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
index dd732eaa6a..b1bae64c4d 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/NettyClientBootstrap.java
@@ -44,8 +44,7 @@ import org.apache.seata.core.protocol.Protocol;
import org.apache.seata.core.rpc.RemotingBootstrap;
import org.apache.seata.core.rpc.netty.grpc.GrpcDecoder;
import org.apache.seata.core.rpc.netty.grpc.GrpcEncoder;
-import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
-import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -97,7 +96,7 @@ public class NettyClientBootstrap implements
RemotingBootstrap {
*
* @param handlers the handlers
*/
- protected void setChannelHandlers(final ChannelHandler... handlers) {
+ public void setChannelHandlers(final ChannelHandler... handlers) {
if (handlers != null) {
channelHandlers = handlers;
}
@@ -146,10 +145,11 @@ public class NettyClientBootstrap implements
RemotingBootstrap {
nettyClientConfig.getChannelMaxReadIdleSeconds(),
nettyClientConfig.getChannelMaxWriteIdleSeconds(),
nettyClientConfig.getChannelMaxAllIdleSeconds()));
- pipeline.addLast(new ProtocolDecoderV1()).addLast(new
ProtocolEncoderV1());
- if (channelHandlers != null) {
- addChannelPipelineLast(ch, channelHandlers);
- }
+ // Use ProtocolEncoderV2 for sending requests (client
always sends latest version)
+ pipeline.addLast(new ProtocolEncoderV2());
+ // Use MultiProtocolDecoder for receiving responses
(supports V0/V1/V2)
+ // channelHandlers will be added by MultiProtocolDecoder
after first message
+ pipeline.addLast(new
MultiProtocolDecoder(channelHandlers));
}
}
});
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
index 509be29a19..9d2cb82857 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++ b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
@@ -26,4 +26,6 @@ import org.apache.seata.core.protocol.RpcMessage;
public interface ProtocolDecoder {
RpcMessage decodeFrame(ByteBuf in);
+
+ byte protocolVersion();
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
index 6c91164fff..09363cc880 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
+++ b/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolEncoder.java
@@ -25,4 +25,6 @@ import org.apache.seata.core.protocol.RpcMessage;
**/
public interface ProtocolEncoder {
void encode(RpcMessage rpcMessage, ByteBuf out);
+
+ byte protocolVersion();
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
index 287898d8f1..1606033695 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolDecoderV0.java
@@ -130,7 +130,7 @@ public class ProtocolDecoderV0 extends
LengthFieldBasedFrameDecoder implements P
System.arraycopy(bs, 0, bs2, 2, length);
byte codecType = isSeataCodec ? SerializerType.SEATA.getCode() :
SerializerType.HESSIAN.getCode();
Serializer serializer =
-
SerializerServiceLoader.load(SerializerType.getByCode(codecType),
ProtocolConstants.VERSION_0);
+
SerializerServiceLoader.load(SerializerType.getByCode(codecType),
protocolVersion());
rpcMessage.setBody(serializer.deserialize(bs2));
} catch (Exception e) {
LOGGER.error("decode error", e);
@@ -151,4 +151,9 @@ public class ProtocolDecoderV0 extends
LengthFieldBasedFrameDecoder implements P
throw new DecodeException(exx);
}
}
+
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_0;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
index 9c30a9dbf5..074ff78e9c 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v0/ProtocolEncoderV0.java
@@ -82,8 +82,7 @@ public class ProtocolEncoderV0 extends MessageToByteEncoder
implements ProtocolE
}
byte[] bodyBytes = null;
- Serializer serializer =
-
SerializerServiceLoader.load(SerializerType.getByCode(codec),
ProtocolConstants.VERSION_0);
+ Serializer serializer =
SerializerServiceLoader.load(SerializerType.getByCode(codec),
protocolVersion());
bodyBytes = serializer.serialize(msg.getBody());
if (msg.isSeataCodec()) {
@@ -119,4 +118,9 @@ public class ProtocolEncoderV0 extends MessageToByteEncoder
implements ProtocolE
LOGGER.error("Encode request error!", e);
}
}
+
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_0;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
index 4bfa09c982..3f2059d098 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolDecoderV1.java
@@ -126,7 +126,7 @@ public class ProtocolDecoderV1 extends
LengthFieldBasedFrameDecoder implements P
bs = compressor.decompress(bs);
SerializerType protocolType =
SerializerType.getByCode(rpcMessage.getCodec());
if (this.supportDeSerializerTypes.contains(protocolType)) {
- Serializer serializer =
SerializerServiceLoader.load(protocolType, ProtocolConstants.VERSION_1);
+ Serializer serializer =
SerializerServiceLoader.load(protocolType, protocolVersion());
rpcMessage.setBody(serializer.deserialize(bs));
} else {
throw new IllegalArgumentException("SerializerType not
match");
@@ -156,4 +156,9 @@ public class ProtocolDecoderV1 extends
LengthFieldBasedFrameDecoder implements P
}
return decoded;
}
+
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_1;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
index 7ac12c6ac4..ffcb92a623 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v1/ProtocolEncoderV1.java
@@ -72,7 +72,7 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder
implements ProtocolE
byte messageType = rpcMessage.getMessageType();
out.writeBytes(ProtocolConstants.MAGIC_CODE_BYTES);
- out.writeByte(ProtocolConstants.VERSION_1);
+ out.writeByte(protocolVersion());
// full Length(4B) and head length(2B) will fix in the end.
out.writerIndex(out.writerIndex() + 6);
out.writeByte(messageType);
@@ -93,7 +93,7 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder
implements ProtocolE
&& messageType !=
ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE) {
// heartbeat has no body
Serializer serializer = SerializerServiceLoader.load(
- SerializerType.getByCode(rpcMessage.getCodec()),
ProtocolConstants.VERSION_1);
+ SerializerType.getByCode(rpcMessage.getCodec()),
protocolVersion());
bodyBytes = serializer.serialize(rpcMessage.getBody());
Compressor compressor =
CompressorFactory.getCompressor(rpcMessage.getCompressor());
bodyBytes = compressor.compress(bodyBytes);
@@ -131,4 +131,9 @@ public class ProtocolEncoderV1 extends MessageToByteEncoder
implements ProtocolE
LOGGER.error("Encode request error!", e);
}
}
+
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_1;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
similarity index 70%
copy from
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to
core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
index 509be29a19..5735be7446 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolDecoderV2.java
@@ -14,16 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.core.rpc.netty.v2;
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
/**
- * the protocol decoder
- *
+ * Decoder of protocol-v2
**/
-public interface ProtocolDecoder {
+public class ProtocolDecoderV2 extends ProtocolDecoderV1 {
- RpcMessage decodeFrame(ByteBuf in);
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_2;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
similarity index 70%
copy from
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to
core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
index 509be29a19..f8149778a0 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/netty/v2/ProtocolEncoderV2.java
@@ -14,16 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.core.rpc.netty.v2;
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
/**
- * the protocol decoder
- *
+ * Encoder of protocol-v2
**/
-public interface ProtocolDecoder {
-
- RpcMessage decodeFrame(ByteBuf in);
+public class ProtocolEncoderV2 extends ProtocolEncoderV1 {
+ @Override
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_2;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
index 10a89ae9c2..88578f446a 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegRmProcessor.java
@@ -82,6 +82,7 @@ public class RegRmProcessor implements RemotingProcessor {
message.getTransactionServiceGroup(),
message.getApplicationId());
}
+ errorInfo = "RM checkAuth fail";
}
} catch (Exception exx) {
isSuccess = false;
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
index 2cc5d4ed57..eba5102577 100644
---
a/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
+++
b/core/src/main/java/org/apache/seata/core/rpc/processor/server/RegTmProcessor.java
@@ -83,6 +83,7 @@ public class RegTmProcessor implements RemotingProcessor {
message.getTransactionServiceGroup(),
message.getApplicationId());
}
+ errorInfo = "TM checkAuth fail";
}
} catch (Exception exx) {
isSuccess = false;
diff --git
a/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
b/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
index 9942e6a505..e46d8b6be6 100644
---
a/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
+++
b/core/src/test/java/org/apache/seata/core/protocol/ProtocolConstantsTest.java
@@ -37,7 +37,7 @@ public class ProtocolConstantsTest {
public void testVersionConstants() {
assertEquals(0, ProtocolConstants.VERSION_0);
assertEquals(1, ProtocolConstants.VERSION_1);
- assertEquals(ProtocolConstants.VERSION_1, ProtocolConstants.VERSION);
+ assertEquals(ProtocolConstants.VERSION_2, ProtocolConstants.VERSION);
}
@Test
diff --git
a/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
index 2651960217..d20da5b7d8 100644
---
a/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
+++
b/mock-server/src/main/java/org/apache/seata/mockserver/processor/MockRegisterProcessor.java
@@ -17,6 +17,8 @@
package org.apache.seata.mockserver.processor;
import io.netty.channel.ChannelHandlerContext;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.AbstractResultMessage;
import org.apache.seata.core.protocol.RegisterRMRequest;
import org.apache.seata.core.protocol.RegisterRMResponse;
import org.apache.seata.core.protocol.RegisterTMRequest;
@@ -46,27 +48,31 @@ public class MockRegisterProcessor implements
RemotingProcessor {
@Override
public void process(ChannelHandlerContext ctx, RpcMessage rpcMessage)
throws Exception {
- if (role == Role.TM) {
- RegisterTMRequest message = (RegisterTMRequest)
rpcMessage.getBody();
- LOGGER.info("message = " + message);
-
- ChannelManager.registerTMChannel(message, ctx.channel());
- Version.putChannelVersion(ctx.channel(), message.getVersion());
-
- RegisterTMResponse resp = new RegisterTMResponse();
- remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), resp);
- LOGGER.info("sendAsyncResponse: {}", resp);
- } else if (role == Role.RM) {
- RegisterRMRequest message = (RegisterRMRequest)
rpcMessage.getBody();
- LOGGER.info("message = " + message);
-
- ChannelManager.registerRMChannel(message, ctx.channel());
- Version.putChannelVersion(ctx.channel(), message.getVersion());
-
- RegisterRMResponse resp = new RegisterRMResponse();
- remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), resp);
- LOGGER.info("sendAsyncResponse: {}", resp);
+ String errorInfo = StringUtils.EMPTY;
+ AbstractResultMessage response = null;
+ try {
+ if (role == Role.TM) {
+ RegisterTMRequest message = (RegisterTMRequest)
rpcMessage.getBody();
+ LOGGER.info("reg message = " + message);
+ ChannelManager.registerTMChannel(message, ctx.channel());
+ Version.putChannelVersion(ctx.channel(), message.getVersion());
+ response = new RegisterTMResponse();
+ } else if (role == Role.RM) {
+ RegisterRMRequest message = (RegisterRMRequest)
rpcMessage.getBody();
+ LOGGER.info("reg message = " + message);
+ ChannelManager.registerRMChannel(message, ctx.channel());
+ Version.putChannelVersion(ctx.channel(), message.getVersion());
+ response = new RegisterRMResponse();
+ }
+ } catch (Exception e) {
+ errorInfo = e.getMessage();
+ LOGGER.error(role + " register fail, error message:{}", errorInfo);
+ }
+ if (StringUtils.isNotEmpty(errorInfo)) {
+ response.setMsg(errorInfo);
}
+ remotingServer.sendAsyncResponse(rpcMessage, ctx.channel(), response);
+ LOGGER.info("sendAsyncResponse: {}", response);
}
public enum Role {
diff --git a/saga/seata-saga-statemachine-designer/package-lock.json
b/saga/seata-saga-statemachine-designer/package-lock.json
index 731a4be522..df02a49409 100644
--- a/saga/seata-saga-statemachine-designer/package-lock.json
+++ b/saga/seata-saga-statemachine-designer/package-lock.json
@@ -97,6 +97,7 @@
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.7.tgz",
"integrity":
"sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==",
"dev": true,
+ "peer": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.23.5",
@@ -2850,6 +2851,7 @@
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity":
"sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2880,6 +2882,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz",
"integrity":
"sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"json-schema-traverse": "^1.0.0",
@@ -3367,6 +3370,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001565",
"electron-to-chromium": "^1.4.601",
@@ -4470,6 +4474,7 @@
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
"integrity":
"sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
"dev": true,
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -4611,6 +4616,7 @@
"resolved":
"https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz",
"integrity":
"sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==",
"dev": true,
+ "peer": true,
"dependencies": {
"array-includes": "^3.1.7",
"array.prototype.findlastindex": "^1.2.3",
@@ -4685,6 +4691,7 @@
"resolved":
"https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.8.0.tgz",
"integrity":
"sha512-Hdh937BS3KdwwbBaKd5+PLCOmYY6U4f2h9Z2ktwtNKvIdIEu137rjYbcb9ApSbVJfWxANNuiKTD/9tOKjK9qOA==",
"dev": true,
+ "peer": true,
"dependencies": {
"@babel/runtime": "^7.23.2",
"aria-query": "^5.3.0",
@@ -4749,6 +4756,7 @@
"resolved":
"https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.33.2.tgz",
"integrity":
"sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==",
"dev": true,
+ "peer": true,
"dependencies": {
"array-includes": "^3.1.6",
"array.prototype.flatmap": "^1.3.1",
@@ -8062,6 +8070,7 @@
"url": "https://github.com/sponsors/ai"
}
],
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.0.0",
@@ -9468,6 +9477,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity":
"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -9618,7 +9628,8 @@
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity":
"sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
- "dev": true
+ "dev": true,
+ "peer": true
},
"node_modules/type-check": {
"version": "0.4.0",
@@ -9912,6 +9923,7 @@
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.95.0.tgz",
"integrity":
"sha512-2t3XstrKULz41MNMBF+cJ97TyHdyQ8HCt//pqErqDvNjU9YQBnZxIHa11VXsi7F3mb5/aO2tuDxdeTPdU7xu9Q==",
"dev": true,
+ "peer": true,
"dependencies": {
"@types/estree": "^1.0.5",
"@webassemblyjs/ast": "^1.12.1",
@@ -9958,6 +9970,7 @@
"resolved":
"https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
"integrity":
"sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
"dev": true,
+ "peer": true,
"dependencies": {
"@discoveryjs/json-ext": "^0.5.0",
"@webpack-cli/configtest": "^1.2.0",
@@ -10148,6 +10161,7 @@
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity":
"sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
+ "peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
diff --git
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
index 07d52825d7..758c3155d4 100644
---
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MessageCodecFactory.java
@@ -16,15 +16,7 @@
*/
package org.apache.seata.serializer.seata;
-import org.apache.seata.core.protocol.AbstractMessage;
-import org.apache.seata.core.protocol.BatchResultMessage;
-import org.apache.seata.core.protocol.MergeResultMessage;
-import org.apache.seata.core.protocol.MergedWarpMessage;
-import org.apache.seata.core.protocol.MessageType;
-import org.apache.seata.core.protocol.RegisterRMRequest;
-import org.apache.seata.core.protocol.RegisterRMResponse;
-import org.apache.seata.core.protocol.RegisterTMRequest;
-import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.*;
import org.apache.seata.core.protocol.transaction.BranchCommitRequest;
import org.apache.seata.core.protocol.transaction.BranchCommitResponse;
import org.apache.seata.core.protocol.transaction.BranchRegisterRequest;
@@ -74,6 +66,8 @@ import
org.apache.seata.serializer.seata.protocol.transaction.GlobalRollbackResp
import
org.apache.seata.serializer.seata.protocol.transaction.GlobalStatusRequestCodec;
import
org.apache.seata.serializer.seata.protocol.transaction.GlobalStatusResponseCodec;
import
org.apache.seata.serializer.seata.protocol.transaction.UndoLogDeleteRequestCodec;
+import org.apache.seata.serializer.seata.protocol.v2.RegisterRMResponseCodecV2;
+import org.apache.seata.serializer.seata.protocol.v2.RegisterTMResponseCodecV2;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
@@ -117,13 +111,21 @@ public class MessageCodecFactory {
msgCodec = new RegisterTMRequestCodec();
break;
case MessageType.TYPE_REG_CLT_RESULT:
- msgCodec = new RegisterTMResponseCodec();
+ if (version == ProtocolConstants.VERSION_2) {
+ msgCodec = new RegisterTMResponseCodecV2();
+ } else {
+ msgCodec = new RegisterTMResponseCodec();
+ }
break;
case MessageType.TYPE_REG_RM:
msgCodec = new RegisterRMRequestCodec();
break;
case MessageType.TYPE_REG_RM_RESULT:
- msgCodec = new RegisterRMResponseCodec();
+ if (version == ProtocolConstants.VERSION_2) {
+ msgCodec = new RegisterRMResponseCodecV2();
+ } else {
+ msgCodec = new RegisterRMResponseCodec();
+ }
break;
case MessageType.TYPE_BRANCH_COMMIT:
msgCodec = new BranchCommitRequestCodec();
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
similarity index 54%
copy from
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
index 7dbce406cd..c8f0950f51 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/MultiVersionCodec.java
@@ -14,35 +14,40 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata;
-import java.io.Serializable;
+import java.util.Map;
/**
- * The type Register rm response.
- *
+ * interface MultiVersionCodec
*/
-public class RegisterRMResponse extends AbstractIdentifyResponse implements
Serializable {
+public interface MultiVersionCodec {
- /**
- * Instantiates a new Register rm response.
- */
- public RegisterRMResponse() {
- this(true);
- }
+ Map<VersionRange, MessageSeataCodec> oldVersionCodec();
/**
- * Instantiates a new Register rm response.
- *
- * @param result the result
+ * version range (begin, end]
*/
- public RegisterRMResponse(boolean result) {
- super();
- setIdentified(result);
- }
+ class VersionRange {
+ private String begin;
+ private String end;
+
+ public VersionRange(String begin, String end) {
+ this.begin = begin;
+ this.end = end;
+ }
+
+ public VersionRange(String end) {
+ this.begin = "0";
+ this.end = end;
+ }
+
+ public String getBegin() {
+ return begin;
+ }
- @Override
- public short getTypeCode() {
- return MessageType.TYPE_REG_RM_RESULT;
+ public String getEnd() {
+ return end;
+ }
}
}
diff --git
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
index c52469eec9..851b3cab03 100644
---
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/SeataSerializer.java
@@ -24,6 +24,7 @@ import org.apache.seata.common.util.BufferUtils;
import org.apache.seata.core.protocol.AbstractMessage;
import org.apache.seata.core.protocol.ProtocolConstants;
import org.apache.seata.core.serializer.Serializer;
+import org.apache.seata.serializer.seata.serializer.SeataSerializerV2;
import java.nio.ByteBuffer;
@@ -39,6 +40,8 @@ public class SeataSerializer implements Serializer {
versionSeataSerializer = SeataSerializerV0.getInstance();
} else if (version == ProtocolConstants.VERSION_1) {
versionSeataSerializer = SeataSerializerV1.getInstance();
+ } else if (version == ProtocolConstants.VERSION_2) {
+ versionSeataSerializer = SeataSerializerV2.getInstance();
}
if (versionSeataSerializer == null) {
throw new UnsupportedOperationException("version is not
supported");
@@ -159,7 +162,7 @@ public class SeataSerializer implements Serializer {
}
}
- private static <T> T deserializeByVersion(byte[] bytes, byte version) {
+ public static <T> T deserializeByVersion(byte[] bytes, byte version) {
if (bytes == null || bytes.length == 0) {
throw new IllegalArgumentException("Nothing to decode.");
}
diff --git
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.java
new file mode 100644
index 0000000000..fb1a559815
--- /dev/null
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/AbstractIdentifyResponseCodecV2.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.seata.serializer.seata.protocol.v2;
+
+import io.netty.buffer.ByteBuf;
+import org.apache.seata.core.protocol.AbstractIdentifyResponse;
+import org.apache.seata.serializer.seata.protocol.AbstractResultMessageCodec;
+
+import java.nio.ByteBuffer;
+
+/**
+ * The type Abstract identify request codec.(v2)
+ **/
+public class AbstractIdentifyResponseCodecV2 extends
AbstractResultMessageCodec {
+ @Override
+ public Class<?> getMessageClassType() {
+ return AbstractIdentifyResponse.class;
+ }
+
+ @Override
+ public <T> void encode(T t, ByteBuf out) {
+ super.encode(t, out);
+ AbstractIdentifyResponse abstractIdentifyResponse =
(AbstractIdentifyResponse) t;
+ boolean identified = abstractIdentifyResponse.isIdentified();
+ String version = abstractIdentifyResponse.getVersion();
+
+ out.writeByte(identified ? (byte) 1 : (byte) 0);
+ if (version != null) {
+ byte[] bs = version.getBytes(UTF8);
+ out.writeShort((short) bs.length);
+ if (bs.length > 0) {
+ out.writeBytes(bs);
+ }
+ } else {
+ out.writeShort((short) 0);
+ }
+ }
+
+ @Override
+ public <T> void decode(T t, ByteBuffer in) {
+ AbstractIdentifyResponse abstractIdentifyResponse =
(AbstractIdentifyResponse) t;
+ super.decode(t, in);
+ abstractIdentifyResponse.setIdentified(in.get() == 1);
+ short len = in.getShort();
+ if (len <= 0) {
+ return;
+ }
+ if (in.remaining() < len) {
+ return;
+ }
+ byte[] bs = new byte[len];
+ in.get(bs);
+ abstractIdentifyResponse.setVersion(new String(bs, UTF8));
+ }
+}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
similarity index 70%
copy from
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
index 509be29a19..a0e9ba08b9 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterRMResponseCodecV2.java
@@ -14,16 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.serializer.seata.protocol.v2;
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.RegisterRMResponse;
/**
- * the protocol decoder
- *
- **/
-public interface ProtocolDecoder {
+ * The type Register rm response codec.(v2)
+ */
+public class RegisterRMResponseCodecV2 extends AbstractIdentifyResponseCodecV2
{
- RpcMessage decodeFrame(ByteBuf in);
+ @Override
+ public Class<?> getMessageClassType() {
+ return RegisterRMResponse.class;
+ }
}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
similarity index 70%
copy from
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
index 509be29a19..9e0e4bc786 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/protocol/v2/RegisterTMResponseCodecV2.java
@@ -14,16 +14,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.rpc.netty;
+package org.apache.seata.serializer.seata.protocol.v2;
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.protocol.RegisterTMResponse;
/**
- * the protocol decoder
- *
- **/
-public interface ProtocolDecoder {
+ * The type Register tm response codec.(v2)
+ */
+public class RegisterTMResponseCodecV2 extends AbstractIdentifyResponseCodecV2
{
- RpcMessage decodeFrame(ByteBuf in);
+ @Override
+ public Class<?> getMessageClassType() {
+ return RegisterTMResponse.class;
+ }
}
diff --git
a/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
new file mode 100644
index 0000000000..c0c863ed73
--- /dev/null
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1.java
@@ -0,0 +1,89 @@
+/*
+ * 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.seata.serializer.seata.serializer;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.seata.common.util.BufferUtils;
+import org.apache.seata.core.protocol.AbstractMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.serializer.Serializer;
+import org.apache.seata.serializer.seata.MessageCodecFactory;
+import org.apache.seata.serializer.seata.MessageSeataCodec;
+import org.apache.seata.serializer.seata.SeataSerializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * SeataSerializer of V1
+ **/
+public class SeataSerializerV1 implements Serializer {
+
+ private static volatile SeataSerializerV1 instance;
+
+ protected SeataSerializerV1() {}
+
+ public static SeataSerializerV1 getInstance() {
+ if (instance == null) {
+ synchronized (SeataSerializerV1.class) {
+ if (instance == null) {
+ instance = new SeataSerializerV1();
+ }
+ }
+ }
+ return instance;
+ }
+
+ @Override
+ public <T> byte[] serialize(T t) {
+ if (!(t instanceof AbstractMessage)) {
+ throw new IllegalArgumentException("AbstractMessage isn't
available.");
+ }
+ AbstractMessage abstractMessage = (AbstractMessage) t;
+ // type code
+ short typecode = abstractMessage.getTypeCode();
+ // msg codec
+ MessageSeataCodec messageCodec =
MessageCodecFactory.getMessageCodec(typecode, protocolVersion());
+ // get empty ByteBuffer
+ ByteBuf out = Unpooled.buffer(1024);
+ // msg encode
+ messageCodec.encode(t, out);
+ byte[] body = new byte[out.readableBytes()];
+ out.readBytes(body);
+
+ ByteBuffer byteBuffer;
+
+ // typecode + body
+ byteBuffer = ByteBuffer.allocate(2 + body.length);
+ byteBuffer.putShort(typecode);
+ byteBuffer.put(body);
+
+ BufferUtils.flip(byteBuffer);
+ byte[] content = new byte[byteBuffer.limit()];
+ byteBuffer.get(content);
+ return content;
+ }
+
+ @Override
+ public <T> T deserialize(byte[] bytes) {
+ return SeataSerializer.deserializeByVersion(bytes, protocolVersion());
+ }
+
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_1;
+ }
+}
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
similarity index 54%
copy from
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to
serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
index 7dbce406cd..6e0c322afc 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++
b/serializer/seata-serializer-seata/src/main/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV2.java
@@ -14,35 +14,30 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata.serializer;
-import java.io.Serializable;
+import org.apache.seata.core.protocol.ProtocolConstants;
/**
- * The type Register rm response.
- *
- */
-public class RegisterRMResponse extends AbstractIdentifyResponse implements
Serializable {
+ * the type SeataSerializer(V2)
+ **/
+public class SeataSerializerV2 extends SeataSerializerV1 {
+ private static volatile SeataSerializerV2 instance;
- /**
- * Instantiates a new Register rm response.
- */
- public RegisterRMResponse() {
- this(true);
- }
+ protected SeataSerializerV2() {}
- /**
- * Instantiates a new Register rm response.
- *
- * @param result the result
- */
- public RegisterRMResponse(boolean result) {
- super();
- setIdentified(result);
+ public static SeataSerializerV2 getInstance() {
+ if (instance == null) {
+ synchronized (SeataSerializerV2.class) {
+ if (instance == null) {
+ instance = new SeataSerializerV2();
+ }
+ }
+ }
+ return instance;
}
- @Override
- public short getTypeCode() {
- return MessageType.TYPE_REG_RM_RESULT;
+ public byte protocolVersion() {
+ return ProtocolConstants.VERSION_2;
}
}
diff --git
a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
similarity index 52%
copy from
core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
copy to
serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
index 7dbce406cd..ae24252c4c 100644
--- a/core/src/main/java/org/apache/seata/core/protocol/RegisterRMResponse.java
+++
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/MultiVersionCodecTest.java
@@ -14,35 +14,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.seata.core.protocol;
+package org.apache.seata.serializer.seata;
-import java.io.Serializable;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
/**
- * The type Register rm response.
- *
+ * Test for MultiVersionCodec.VersionRange
*/
-public class RegisterRMResponse extends AbstractIdentifyResponse implements
Serializable {
-
- /**
- * Instantiates a new Register rm response.
- */
- public RegisterRMResponse() {
- this(true);
- }
+public class MultiVersionCodecTest {
- /**
- * Instantiates a new Register rm response.
- *
- * @param result the result
- */
- public RegisterRMResponse(boolean result) {
- super();
- setIdentified(result);
+ @Test
+ public void testVersionRangeWithBeginAndEnd() {
+ MultiVersionCodec.VersionRange range = new
MultiVersionCodec.VersionRange("1.0", "2.0");
+ assertThat(range.getBegin()).isEqualTo("1.0");
+ assertThat(range.getEnd()).isEqualTo("2.0");
}
- @Override
- public short getTypeCode() {
- return MessageType.TYPE_REG_RM_RESULT;
+ @Test
+ public void testVersionRangeWithOnlyEnd() {
+ MultiVersionCodec.VersionRange range = new
MultiVersionCodec.VersionRange("2.0");
+ assertThat(range.getBegin()).isEqualTo("0");
+ assertThat(range.getEnd()).isEqualTo("2.0");
}
}
diff --git
a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
new file mode 100644
index 0000000000..adcb3095ac
--- /dev/null
+++
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/protocol/v2/RegisterResponseCodecV2Test.java
@@ -0,0 +1,89 @@
+/*
+ * 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.seata.serializer.seata.protocol.v2;
+
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterRMResponse;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.ResultCode;
+import org.apache.seata.serializer.seata.SeataSerializer;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Test for RegisterTMResponseCodecV2 and RegisterRMResponseCodecV2
+ */
+public class RegisterResponseCodecV2Test {
+
+ private final SeataSerializer seataSerializer = new
SeataSerializer(ProtocolConstants.VERSION_2);
+
+ @Test
+ public void testRegisterTMResponseCodecV2() {
+ RegisterTMResponse response = new RegisterTMResponse();
+ response.setIdentified(true);
+ response.setVersion("2.0.0");
+ response.setResultCode(ResultCode.Success);
+ response.setMsg("success");
+
+ byte[] bytes = seataSerializer.serialize(response);
+ assertThat(bytes).isNotNull();
+
+ RegisterTMResponse deserialized = seataSerializer.deserialize(bytes);
+ assertThat(deserialized.isIdentified()).isTrue();
+ assertThat(deserialized.getVersion()).isEqualTo("2.0.0");
+ }
+
+ @Test
+ public void testRegisterTMResponseCodecV2WithNullVersion() {
+ RegisterTMResponse response = new RegisterTMResponse();
+ response.setIdentified(false);
+ response.setVersion(null);
+ response.setResultCode(ResultCode.Failed);
+
+ byte[] bytes = seataSerializer.serialize(response);
+ RegisterTMResponse deserialized = seataSerializer.deserialize(bytes);
+ assertThat(deserialized.isIdentified()).isFalse();
+ }
+
+ @Test
+ public void testRegisterRMResponseCodecV2() {
+ RegisterRMResponse response = new RegisterRMResponse();
+ response.setIdentified(true);
+ response.setVersion("2.0.0");
+ response.setResultCode(ResultCode.Success);
+
+ byte[] bytes = seataSerializer.serialize(response);
+ assertThat(bytes).isNotNull();
+
+ RegisterRMResponse deserialized = seataSerializer.deserialize(bytes);
+ assertThat(deserialized.isIdentified()).isTrue();
+ assertThat(deserialized.getVersion()).isEqualTo("2.0.0");
+ }
+
+ @Test
+ public void testRegisterTMResponseCodecV2GetMessageClassType() {
+ RegisterTMResponseCodecV2 codec = new RegisterTMResponseCodecV2();
+
assertThat(codec.getMessageClassType()).isEqualTo(RegisterTMResponse.class);
+ }
+
+ @Test
+ public void testRegisterRMResponseCodecV2GetMessageClassType() {
+ RegisterRMResponseCodecV2 codec = new RegisterRMResponseCodecV2();
+
assertThat(codec.getMessageClassType()).isEqualTo(RegisterRMResponse.class);
+ }
+}
diff --git
a/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
new file mode 100644
index 0000000000..ea4b7017e5
--- /dev/null
+++
b/serializer/seata-serializer-seata/src/test/java/org/apache/seata/serializer/seata/serializer/SeataSerializerV1Test.java
@@ -0,0 +1,82 @@
+/*
+ * 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.seata.serializer.seata.serializer;
+
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.ResultCode;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * Test for SeataSerializerV1
+ */
+public class SeataSerializerV1Test {
+
+ private final SeataSerializerV1 serializer =
SeataSerializerV1.getInstance();
+
+ @Test
+ public void testGetInstance() {
+ SeataSerializerV1 instance1 = SeataSerializerV1.getInstance();
+ SeataSerializerV1 instance2 = SeataSerializerV1.getInstance();
+ assertThat(instance1).isSameAs(instance2);
+ }
+
+ @Test
+ public void testProtocolVersion() {
+
assertThat(serializer.protocolVersion()).isEqualTo(ProtocolConstants.VERSION_1);
+ }
+
+ @Test
+ public void testSerializeAndDeserializeRegisterTMRequest() {
+ RegisterTMRequest request = new RegisterTMRequest("testApp",
"testGroup");
+ request.setExtraData("extra");
+
+ byte[] bytes = serializer.serialize(request);
+ assertThat(bytes).isNotNull();
+ assertThat(bytes.length).isGreaterThan(0);
+
+ RegisterTMRequest deserialized = serializer.deserialize(bytes);
+ assertThat(deserialized.getApplicationId()).isEqualTo("testApp");
+
assertThat(deserialized.getTransactionServiceGroup()).isEqualTo("testGroup");
+ }
+
+ @Test
+ public void testSerializeAndDeserializeRegisterTMResponse() {
+ RegisterTMResponse response = new RegisterTMResponse();
+ response.setIdentified(true);
+ response.setVersion("1.0.0");
+ response.setResultCode(ResultCode.Success);
+
+ byte[] bytes = serializer.serialize(response);
+ assertThat(bytes).isNotNull();
+
+ RegisterTMResponse deserialized = serializer.deserialize(bytes);
+ assertThat(deserialized.isIdentified()).isTrue();
+ assertThat(deserialized.getVersion()).isEqualTo("1.0.0");
+ }
+
+ @Test
+ public void testSerializeNonAbstractMessageThrows() {
+ assertThrows(IllegalArgumentException.class, () -> {
+ serializer.serialize("not an AbstractMessage");
+ });
+ }
+}
diff --git
a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
similarity index 55%
copy from
core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
copy to
test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
index 509be29a19..11e2f02184 100644
--- a/core/src/main/java/org/apache/seata/core/rpc/netty/ProtocolDecoder.java
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/CodecTestCheckAuthHandler.java
@@ -16,14 +16,23 @@
*/
package org.apache.seata.core.rpc.netty;
-import io.netty.buffer.ByteBuf;
-import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.common.loader.LoadLevel;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.server.auth.DefaultCheckAuthHandler;
/**
- * the protocol decoder
- *
+ * the type CodecTestCheckAuthHandler
**/
-public interface ProtocolDecoder {
+@LoadLevel(name = "codecTestCheckAuthHandler", order = 101)
+public class CodecTestCheckAuthHandler extends DefaultCheckAuthHandler {
+
+ public static String CODEC_TEST_REG_ERROR = "codec_test_reg_error";
- RpcMessage decodeFrame(ByteBuf in);
+ @Override
+ public boolean regTransactionManagerCheckAuth(RegisterTMRequest request) {
+ if (CODEC_TEST_REG_ERROR.equals(request.getExtraData())) {
+ return false;
+ }
+ return super.regTransactionManagerCheckAuth(request);
+ }
}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
new file mode 100644
index 0000000000..c1881cd37a
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/MultiProtocolDecoderTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.seata.core.rpc.netty;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandler;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.embedded.EmbeddedChannel;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.rpc.netty.v1.ProtocolDecoderV1;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.core.rpc.netty.v2.ProtocolEncoderV2;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+/**
+ * Test for MultiProtocolDecoder encoder replacement functionality
+ */
+public class MultiProtocolDecoderTest {
+
+ /**
+ * Test that MultiProtocolDecoder correctly removes existing encoder when
processing messages
+ */
+ @Test
+ public void testEncoderReplacement() throws Exception {
+ // Create an embedded channel with V2 encoder and MultiProtocolDecoder
+ EmbeddedChannel channel = new EmbeddedChannel();
+
+ // Add V2 encoder first (simulating client-side setup)
+ channel.pipeline().addLast("encoder", new ProtocolEncoderV2());
+
+ // Add MultiProtocolDecoder
+ channel.pipeline().addLast("multiDecoder",
createMultiProtocolDecoder(ProtocolConstants.VERSION_1));
+
+ // Create a V1 message (magic + version + length + data)
+ ByteBuf testMessage = createV1TestMessage();
+
+ // Process the message through MultiProtocolDecoder
+ channel.writeInbound(testMessage);
+
+ // Verify that the original V2 encoder was removed and V1 encoder was
added
+ ChannelPipeline pipeline = channel.pipeline();
+
+ // The MultiProtocolDecoder should have been removed after processing
+ assertNull(pipeline.get("multiDecoder"), "MultiProtocolDecoder should
be removed after processing");
+
+ // Should not have the old V2 encoder anymore
+ assertNull(pipeline.get("encoder"), "Original V2 encoder should be
removed");
+
+ // Should have V1 encoder and decoder
+ assertNotNull(pipeline.get(ProtocolDecoderV1.class), "Should have V1
decoder");
+ assertNotNull(pipeline.get(ProtocolEncoderV1.class), "Should have V1
encoder");
+
+ channel.close();
+ }
+
+ /**
+ * Test that no encoder duplication occurs
+ */
+ @Test
+ public void testNoEncoderDuplication() throws Exception {
+ EmbeddedChannel channel = new EmbeddedChannel();
+
+ // Add V1 encoder first
+ channel.pipeline().addLast("v1encoder", new ProtocolEncoderV1());
+
+ // Add MultiProtocolDecoder
+ channel.pipeline().addLast("multiDecoder", new MultiProtocolDecoder());
+
+ // Create a V1 message
+ ByteBuf testMessage = createV1TestMessage();
+
+ // Process the message
+ channel.writeInbound(testMessage);
+
+ // Count encoders in the pipeline
+ long encoderCount = channel.pipeline().toMap().values().stream()
+ .filter(handler -> handler instanceof ProtocolEncoder &&
!(handler instanceof ProtocolDecoder))
+ .count();
+
+ // Should have exactly one encoder
+ assertEquals(1, encoderCount, "Should have exactly one encoder after
processing");
+
+ channel.close();
+ }
+
+ /**
+ * Create a test V1 message
+ */
+ private ByteBuf createV1TestMessage() {
+ ByteBuf buffer = Unpooled.buffer();
+
+ // Magic code (0xdada)
+ buffer.writeBytes(ProtocolConstants.MAGIC_CODE_BYTES);
+
+ // Version (V1)
+ buffer.writeByte(ProtocolConstants.VERSION_1);
+
+ // Full length (4 bytes) - we'll update this
+ int lengthIndex = buffer.writerIndex();
+ buffer.writeInt(0); // placeholder
+
+ // Head length (2 bytes)
+ buffer.writeShort(16);
+
+ // Message type
+ buffer.writeByte(ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
+
+ // Serializer
+ buffer.writeByte(1);
+
+ // Compressor
+ buffer.writeByte(0);
+
+ // Request ID (4 bytes)
+ buffer.writeInt(1);
+
+ // Update full length
+ int fullLength = buffer.readableBytes();
+ buffer.setInt(lengthIndex, fullLength);
+
+ return buffer;
+ }
+
+ public MultiProtocolDecoder createMultiProtocolDecoder(byte
currentVersion) {
+ return new MultiProtocolDecoder(currentVersion, new
TestServerHandler());
+ }
+
+ public MultiProtocolDecoder createMultiProtocolDecoder(byte
currentVersion, ChannelHandler... handlers) {
+ return new MultiProtocolDecoder(currentVersion, handlers);
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.java
new file mode 100644
index 0000000000..5d6dd1ffdd
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestClientHandler.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.seata.core.rpc.netty;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.apache.seata.core.protocol.RpcMessage;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Test client handler that captures server responses.
+ * Supports multiple request/response cycles using resetLatch().
+ */
+public class TestClientHandler extends ChannelInboundHandlerAdapter {
+
+ private final AtomicReference<Object> responseRef;
+ private volatile CountDownLatch responseLatch;
+
+ public TestClientHandler(AtomicReference<Object> responseRef,
CountDownLatch responseLatch) {
+ this.responseRef = responseRef;
+ this.responseLatch = responseLatch;
+ }
+
+ public TestClientHandler() {
+ this.responseRef = new AtomicReference<>();
+ this.responseLatch = new CountDownLatch(1);
+ }
+
+ /**
+ * Reset the latch for a new request/response cycle
+ */
+ public void resetLatch(CountDownLatch newLatch) {
+ this.responseLatch = newLatch;
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
+ // Handle RpcMessage wrapped responses
+ if (msg instanceof RpcMessage) {
+ RpcMessage rpcMessage = (RpcMessage) msg;
+ Object body = rpcMessage.getBody();
+ responseRef.set(body);
+ } else {
+ // Handle direct responses (for backward compatibility)
+ responseRef.set(msg);
+ }
+ if (responseLatch != null) {
+ responseLatch.countDown();
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.java
new file mode 100644
index 0000000000..bb0299078f
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/TestServerHandler.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.seata.core.rpc.netty;
+
+import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelInboundHandlerAdapter;
+import org.apache.seata.core.protocol.HeartbeatMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.RpcMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * Test server handler that simulates seata server behavior
+ */
+public class TestServerHandler extends ChannelInboundHandlerAdapter {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(TestServerHandler.class);
+
+ private final AtomicReference<Object> requestRef;
+ private final Consumer<Object> responseCallback;
+
+ public TestServerHandler(AtomicReference<Object> requestRef,
Consumer<Object> responseCallback) {
+ this.requestRef = requestRef;
+ this.responseCallback = responseCallback;
+ }
+
+ public TestServerHandler() {
+ this.requestRef = new AtomicReference<>();
+ this.responseCallback = null;
+ }
+
+ @Override
+ public void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
+ requestRef.set(msg);
+
+ // Handle RpcMessage wrapped requests
+ if (msg instanceof RpcMessage) {
+ RpcMessage rpcMessage = (RpcMessage) msg;
+ Object body = rpcMessage.getBody();
+
+ if (body instanceof RegisterTMRequest) {
+ handleRegisterTMRequest(ctx, rpcMessage, (RegisterTMRequest)
body);
+ } else if (body instanceof HeartbeatMessage) {
+ handleHeartbeatMessage(ctx, rpcMessage, (HeartbeatMessage)
body);
+ }
+ } else if (msg instanceof RegisterTMRequest) {
+ // Handle direct requests (for backward compatibility)
+ handleDirectRegisterTMRequest(ctx, (RegisterTMRequest) msg);
+ } else if (msg instanceof HeartbeatMessage) {
+ // Handle direct heartbeat (for backward compatibility)
+ handleDirectHeartbeatMessage(ctx, (HeartbeatMessage) msg);
+ }
+ }
+
+ private void handleRegisterTMRequest(ChannelHandlerContext ctx, RpcMessage
rpcMessage, RegisterTMRequest request) {
+ boolean identified = true;
+ String respMsg = "";
+
+ // Check for auth error flag (mimic real server auth logic)
+ if
(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR.equals(request.getExtraData()))
{
+ identified = false;
+ respMsg = "Auth Failed";
+ }
+
+ RegisterTMResponse response = new RegisterTMResponse(identified);
+ response.setVersion(request.getVersion());
+ response.setMsg(respMsg);
+
+ // Wrap response in RpcMessage
+ RpcMessage responseMsg = new RpcMessage();
+ responseMsg.setId(rpcMessage.getId());
+ responseMsg.setMessageType(ProtocolConstants.MSGTYPE_RESPONSE);
+ responseMsg.setCodec(rpcMessage.getCodec());
+ responseMsg.setCompressor(rpcMessage.getCompressor());
+ responseMsg.setBody(response);
+
+ ctx.writeAndFlush(responseMsg);
+ }
+
+ private void handleHeartbeatMessage(ChannelHandlerContext ctx, RpcMessage
rpcMessage, HeartbeatMessage heartbeat) {
+ if (heartbeat.isPing()) {
+ LOGGER.debug("Received PING, sending PONG");
+ // Respond with PONG
+ RpcMessage responseMsg = new RpcMessage();
+ responseMsg.setId(rpcMessage.getId());
+
responseMsg.setMessageType(ProtocolConstants.MSGTYPE_HEARTBEAT_RESPONSE);
+ responseMsg.setCodec(rpcMessage.getCodec());
+ responseMsg.setCompressor(rpcMessage.getCompressor());
+ responseMsg.setBody(HeartbeatMessage.PONG);
+
+ ctx.writeAndFlush(responseMsg);
+ }
+ }
+
+ private void handleDirectRegisterTMRequest(ChannelHandlerContext ctx,
RegisterTMRequest request) {
+ boolean identified = true;
+
+ if
(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR.equals(request.getExtraData()))
{
+ identified = false;
+ }
+
+ RegisterTMResponse response = new RegisterTMResponse(identified);
+ response.setVersion(request.getVersion());
+ ctx.writeAndFlush(response);
+ }
+
+ private void handleDirectHeartbeatMessage(ChannelHandlerContext ctx,
HeartbeatMessage heartbeat) {
+ if (heartbeat.isPing()) {
+ ctx.writeAndFlush(HeartbeatMessage.PONG);
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
+ cause.printStackTrace();
+ ctx.close();
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
new file mode 100644
index 0000000000..5c806385d4
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/MultiVersionCompatibilityTest.java
@@ -0,0 +1,405 @@
+/*
+ * 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.seata.core.rpc.netty.multiversion;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import io.netty.bootstrap.Bootstrap;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelInitializer;
+import io.netty.channel.ChannelOption;
+import io.netty.channel.ChannelPipeline;
+import io.netty.channel.EventLoopGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.SocketChannel;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.channel.socket.nio.NioSocketChannel;
+import io.netty.handler.codec.MessageToByteEncoder;
+import io.netty.handler.timeout.IdleStateHandler;
+import org.apache.seata.common.XID;
+import org.apache.seata.common.metadata.Instance;
+import org.apache.seata.common.metadata.Node;
+import org.apache.seata.common.thread.NamedThreadFactory;
+import org.apache.seata.common.util.NetUtil;
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.common.util.UUIDGenerator;
+import org.apache.seata.core.protocol.HeartbeatMessage;
+import org.apache.seata.core.protocol.ProtocolConstants;
+import org.apache.seata.core.protocol.RegisterTMRequest;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.protocol.RpcMessage;
+import org.apache.seata.core.rpc.netty.MultiProtocolDecoderTest;
+import org.apache.seata.core.rpc.netty.NettyClientBootstrap;
+import org.apache.seata.core.rpc.netty.NettyClientConfig;
+import org.apache.seata.core.rpc.netty.NettyPoolKey;
+import org.apache.seata.core.rpc.netty.NettyServerConfig;
+import org.apache.seata.core.rpc.netty.TestClientHandler;
+import org.apache.seata.core.rpc.netty.TestServerHandler;
+import org.apache.seata.core.rpc.netty.v1.ProtocolEncoderV1;
+import org.apache.seata.mockserver.MockCoordinator;
+import org.apache.seata.mockserver.MockNettyRemotingServer;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetSocketAddress;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This provides common utilities for testing multi-version protocol
compatibility.
+ * Supports testing all 2x2 combinations:
+ * - V1 Server + V1 Client (manual construction - simulates legacy)
+ * - V1 Server + V2 Client (manual construction - simulates legacy)
+ * - V2 Server + V1 Client (MockNettyRemotingServer + manual client)
+ * - V2 Server + V2 Client (MockNettyRemotingServer + NettyClientBootstrap -
production-like)
+ */
+public abstract class MultiVersionCompatibilityTest {
+
+ // LOG instance
+ private static final Logger LOGGER =
LoggerFactory.getLogger(MultiVersionCompatibilityTest.class);
+
+ // JSON ObjectMapper for pretty printing
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
+ .enable(SerializationFeature.INDENT_OUTPUT)
+ .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
+
+ /**
+ * Convert object to pretty JSON format for logging
+ */
+ public static String toPrettyJson(Object obj) {
+ if (obj == null) {
+ return "null";
+ }
+ try {
+ return OBJECT_MAPPER.writeValueAsString(obj);
+ } catch (JsonProcessingException e) {
+ LOGGER.warn("Failed to convert object to JSON, using toString():
{}", e.getMessage());
+ return obj.toString();
+ }
+ }
+
+ // ========== V1 Server (manual construction for legacy simulation)
==========
+ protected EventLoopGroup bossGroup;
+ protected EventLoopGroup workerGroup;
+ protected Channel serverChannel;
+
+ // ========== V2 Server (using production MockNettyRemotingServer)
==========
+ protected MockNettyRemotingServer mockRemotingServer;
+ protected ThreadPoolExecutor serverWorkingThreads;
+
+ // ========== V1 Client (manual construction for legacy simulation)
==========
+ protected EventLoopGroup clientGroup;
+ protected Channel clientChannel;
+
+ // ========== V2 Client (using production NettyClientBootstrap) ==========
+ protected NettyClientBootstrap clientBootstrap;
+
+ // ========== Common ==========
+ protected TestClientHandler testClientHandler;
+ protected final AtomicReference<Object> requestRef = new
AtomicReference<>();
+ protected final AtomicReference<Object> responseRef = new
AtomicReference<>();
+ protected CountDownLatch responseLatch;
+
+ // Helper for creating MultiProtocolDecoder with specific version (for V1
tests)
+ private final MultiProtocolDecoderTest decoderTestHelper = new
MultiProtocolDecoderTest();
+
+ @BeforeEach
+ public void setUp() {
+ bossGroup = new NioEventLoopGroup(1);
+ workerGroup = new NioEventLoopGroup();
+ clientGroup = new NioEventLoopGroup();
+ requestRef.set(null);
+ responseRef.set(null);
+ responseLatch = new CountDownLatch(1);
+ }
+
+ @AfterEach
+ public void tearDown() throws InterruptedException {
+ // Shutdown client
+ if (clientChannel != null) {
+ clientChannel.close().sync();
+ }
+ if (clientBootstrap != null) {
+ clientBootstrap.shutdown();
+ }
+
+ // Shutdown V1 server
+ if (serverChannel != null) {
+ serverChannel.close().sync();
+ }
+
+ // Shutdown V2 server (MockNettyRemotingServer)
+ if (mockRemotingServer != null) {
+ mockRemotingServer.destroy();
+ }
+ if (serverWorkingThreads != null) {
+ serverWorkingThreads.shutdown();
+ }
+
+ bossGroup.shutdownGracefully().sync();
+ workerGroup.shutdownGracefully().sync();
+ clientGroup.shutdownGracefully().sync();
+ }
+
+ // ==================== V1 Server Methods (manual, for legacy simulation)
====================
+
+ /**
+ * Start a V1 protocol server (manual construction to simulate legacy
server)
+ */
+ protected void startV1Server(int port) throws InterruptedException {
+ startServerByVersion(ProtocolConstants.VERSION_1, port);
+ }
+
+ private void startServerByVersion(byte version, int port) throws
InterruptedException {
+ ServerBootstrap serverBootstrap = new ServerBootstrap();
+ serverBootstrap
+ .group(bossGroup, workerGroup)
+ .channel(NioServerSocketChannel.class)
+ .childHandler(new ChannelInitializer<SocketChannel>() {
+ @Override
+ protected void initChannel(SocketChannel ch) throws
Exception {
+ ChannelPipeline pipeline = ch.pipeline();
+ pipeline.addLast(new IdleStateHandler(0, 0, 30));
+ pipeline.addLast(
+
decoderTestHelper.createMultiProtocolDecoder(version,
createTestServerHandler()));
+ }
+ });
+
+ ChannelFuture future = serverBootstrap.bind(port).sync();
+ serverChannel = future.channel();
+ LOGGER.info("V1 Server started on port {} (manual construction)",
port);
+ }
+
+ // ==================== V2 Server Methods (using MockNettyRemotingServer)
====================
+
+ /**
+ * Start a V2 protocol server using production MockNettyRemotingServer.
+ * This uses the real server bootstrap with all production handlers:
+ * - ProtocolDetectHandler -> SeataDetector -> MultiProtocolDecoder
+ * - MockRegisterProcessor (handles TM/RM registration)
+ * - MockHeartbeatProcessor (handles heartbeat)
+ */
+ protected void startV2Server(int port) {
+ serverWorkingThreads = new ThreadPoolExecutor(
+ 10,
+ 10,
+ 500,
+ TimeUnit.SECONDS,
+ new LinkedBlockingQueue<>(2000),
+ new NamedThreadFactory("MockServerThread", 10),
+ new ThreadPoolExecutor.CallerRunsPolicy());
+
+ NettyServerConfig config = new NettyServerConfig();
+ config.setServerListenPort(port);
+ mockRemotingServer = new MockNettyRemotingServer(serverWorkingThreads,
config);
+
+ // Initialize XID for the server
+ XID.setIpAddress(NetUtil.getLocalIp());
+ XID.setPort(port);
+ Instance.getInstance().setTransaction(new
Node.Endpoint(XID.getIpAddress(), XID.getPort(), "netty"));
+ UUIDGenerator.init(1L);
+
+ MockCoordinator coordinator = MockCoordinator.getInstance();
+ coordinator.setRemotingServer(mockRemotingServer);
+ mockRemotingServer.setHandler(coordinator);
+ mockRemotingServer.init();
+
+ LOGGER.info("V2 Server started on port {} (using
MockNettyRemotingServer)", port);
+ }
+
+ // ==================== V1 Client Methods (manual, for legacy simulation)
====================
+
+ /**
+ * Connect V1 client (manual construction to simulate legacy client)
+ */
+ protected void connectV1Client(String host, int port, int connectTimeout) {
+ connectClientByVersion(new ProtocolEncoderV1(),
ProtocolConstants.VERSION_1, host, port, connectTimeout);
+ }
+
+ private void connectClientByVersion(
+ MessageToByteEncoder encoder, byte version, String host, int port,
int connectTimeout) {
+ testClientHandler = createTestClientHandler();
+
+ Bootstrap bootstrap = new Bootstrap();
+ bootstrap.group(clientGroup).channel(NioSocketChannel.class);
+ bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
+ bootstrap.option(ChannelOption.ALLOCATOR, ByteBufAllocator.DEFAULT);
+ bootstrap.handler(new ChannelInitializer<Channel>() {
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ ChannelPipeline pipeline = channel.pipeline();
+ pipeline.addLast(new IdleStateHandler(0, 0, 15));
+ pipeline.addLast(encoder);
+
pipeline.addLast(decoderTestHelper.createMultiProtocolDecoder(version,
testClientHandler));
+ }
+ });
+
+ ChannelFuture channelFuture = bootstrap.connect(host, port);
+ channelFuture.awaitUninterruptibly(connectTimeout,
TimeUnit.MILLISECONDS);
+ if (channelFuture.isSuccess()) {
+ clientChannel = channelFuture.channel();
+ LOGGER.info("V1 Client connected to {}:{} (manual construction)",
host, port);
+ }
+ }
+
+ // ==================== V2 Client Methods (using NettyClientBootstrap)
====================
+
+ /**
+ * Connect V2 client using production NettyClientBootstrap.
+ * This uses the real client bootstrap with:
+ * - ProtocolEncoderV2
+ * - MultiProtocolDecoder
+ */
+ protected void connectV2Client(String host, int port, int connectTimeout) {
+ testClientHandler = createTestClientHandler();
+
+ NettyClientConfig config = new NettyClientConfig();
+ config.setConnectTimeoutMillis(connectTimeout);
+
+ clientBootstrap = new NettyClientBootstrap(config,
NettyPoolKey.TransactionRole.TMROLE);
+ clientBootstrap.setChannelHandlers(testClientHandler);
+ clientBootstrap.start();
+
+ clientChannel = clientBootstrap.getNewChannel(new
InetSocketAddress(host, port));
+ LOGGER.info("V2 Client connected to {}:{} (using
NettyClientBootstrap)", host, port);
+ }
+
+ // ==================== Request/Response Methods ====================
+
+ /**
+ * Send request through client channel
+ */
+ protected void sendRequest(Object request) {
+ if (clientChannel != null && clientChannel.isActive()) {
+ RpcMessage rpcMessage = buildRequestMessage(request);
+ clientChannel.writeAndFlush(rpcMessage);
+ }
+ }
+
+ /**
+ * Send heartbeat PING through client channel
+ */
+ protected void sendHeartbeatPing() {
+ if (clientChannel != null && clientChannel.isActive()) {
+ RpcMessage rpcMessage = new RpcMessage();
+ rpcMessage.setId(getNextMessageId());
+
rpcMessage.setMessageType(ProtocolConstants.MSGTYPE_HEARTBEAT_REQUEST);
+ rpcMessage.setCodec(ProtocolConstants.CONFIGURED_CODEC);
+ rpcMessage.setCompressor(ProtocolConstants.CONFIGURED_COMPRESSOR);
+ rpcMessage.setBody(HeartbeatMessage.PING);
+ clientChannel.writeAndFlush(rpcMessage);
+ }
+ }
+
+ private RpcMessage buildRequestMessage(Object msg) {
+ RpcMessage rpcMessage = new RpcMessage();
+ rpcMessage.setId(getNextMessageId());
+ rpcMessage.setMessageType(ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
+ rpcMessage.setCodec(ProtocolConstants.CONFIGURED_CODEC);
+ rpcMessage.setCompressor(ProtocolConstants.CONFIGURED_COMPRESSOR);
+ rpcMessage.setBody(msg);
+ return rpcMessage;
+ }
+
+ private static final AtomicInteger MESSAGE_ID_GENERATOR = new
AtomicInteger(0);
+
+ private int getNextMessageId() {
+ return MESSAGE_ID_GENERATOR.incrementAndGet();
+ }
+
+ // ==================== Handler Factory Methods ====================
+
+ /**
+ * Create test server handler (for V1 server manual construction)
+ */
+ protected TestServerHandler createTestServerHandler() {
+ return new TestServerHandler(requestRef, null);
+ }
+
+ /**
+ * Create test client handler (needed to capture responses for
verification)
+ */
+ protected TestClientHandler createTestClientHandler() {
+ return new TestClientHandler(responseRef, responseLatch);
+ }
+
+ /**
+ * Reset response latch for next request/response cycle
+ */
+ protected void resetResponseLatch() {
+ responseLatch = new CountDownLatch(1);
+ responseRef.set(null);
+ if (testClientHandler != null) {
+ testClientHandler.resetLatch(responseLatch);
+ }
+ }
+
+ // ==================== Test Helper Methods ====================
+
+ @NotNull
+ protected RegisterTMResponse doSendRegister(String extraData) throws
InterruptedException {
+ RegisterTMRequest request = new RegisterTMRequest("testApp",
"testGroup");
+ if (StringUtils.isNotBlank(extraData)) {
+ request.setExtraData(extraData);
+ }
+ LOGGER.info("Sending RegisterTMRequest:\n{}", toPrettyJson(request));
+ sendRequest(request);
+
+ boolean received = responseLatch.await(30, TimeUnit.SECONDS);
+ Assertions.assertTrue(received, "Should receive response within
timeout");
+
+ Object response = responseRef.get();
+ Assertions.assertNotNull(response, "Should receive response from
server");
+ LOGGER.info("Received RegisterTMResponse:\n{}",
toPrettyJson(response));
+
+ RegisterTMResponse tmResponse = (RegisterTMResponse) response;
+ return tmResponse;
+ }
+
+ /**
+ * Send heartbeat PING and verify PONG response.
+ */
+ protected void doSendHeartbeatAndVerify() throws InterruptedException {
+ resetResponseLatch();
+
+ LOGGER.info("Sending HeartbeatMessage: PING");
+ sendHeartbeatPing();
+
+ boolean received = responseLatch.await(30, TimeUnit.SECONDS);
+ Assertions.assertTrue(received, "Should receive heartbeat response
within timeout");
+
+ Object response = responseRef.get();
+ Assertions.assertNotNull(response, "Should receive heartbeat response
from server");
+ LOGGER.info("Received HeartbeatMessage: {}", response);
+
+ Assertions.assertInstanceOf(HeartbeatMessage.class, response,
"Response should be HeartbeatMessage");
+ HeartbeatMessage heartbeatResponse = (HeartbeatMessage) response;
+ Assertions.assertFalse(heartbeatResponse.isPing(), "Response should be
PONG (isPing=false)");
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
new file mode 100644
index 0000000000..b57e297718
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV1Test.java
@@ -0,0 +1,70 @@
+/*
+ * 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.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V1 Server with V1 Client compatibility.
+ * Both server and client use V1 protocol (manual construction to simulate
legacy systems).
+ * - Server: Manual V1 construction with TestServerHandler
+ * - Client: Manual V1 construction with ProtocolEncoderV1
+ */
+public class ServerV1ToClientV1Test extends MultiVersionCompatibilityTest {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ServerV1ToClientV1Test.class);
+
+ /**
+ * Test V1 client to V1 server - success scenario.
+ * Both sides use manual construction to simulate legacy V1 systems.
+ */
+ @Test
+ public void testServerV1ToClientV1Success() throws Exception {
+ // Manual V1 server construction (simulates legacy server)
+ startV1Server(8111);
+ // Manual V1 client construction (simulates legacy client)
+ connectV1Client("127.0.0.1", 8111, 3000);
+
+ // Step 1: Register TM
+ RegisterTMResponse tmResponse = doSendRegister(null);
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified");
+
+ // Step 2: Send heartbeat to verify bidirectional encode/decode
+ doSendHeartbeatAndVerify();
+ LOGGER.info("Heartbeat PING/PONG verified - bidirectional
communication works (V1 Server + V1 Client)");
+ }
+
+ /**
+ * Test V1 client to V1 server - failure scenario (auth error).
+ * Note: V1 protocol does not include error message in response.
+ */
+ @Test
+ public void testServerV1ToClientV1AuthFailure() throws Exception {
+ startV1Server(8112);
+ connectV1Client("127.0.0.1", 8112, 3000);
+
+ RegisterTMResponse tmResponse =
doSendRegister(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR);
+ Assertions.assertFalse(tmResponse.isIdentified(), "Should fail
identification due to auth error");
+ Assertions.assertTrue(StringUtils.isBlank(tmResponse.getMsg()), "V1
protocol should not include error message");
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
new file mode 100644
index 0000000000..3a6f668bf4
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV1ToClientV2Test.java
@@ -0,0 +1,71 @@
+/*
+ * 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.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.common.util.StringUtils;
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V1 Server with V2 Client compatibility.
+ * - Server: Manual V1 construction with TestServerHandler (simulates legacy
server)
+ * - Client: NettyClientBootstrap (production V2 client with
MultiProtocolDecoder)
+ *
+ * This tests client auto-downgrade - V2 client connecting to V1 server.
+ */
+public class ServerV1ToClientV2Test extends MultiVersionCompatibilityTest {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ServerV1ToClientV2Test.class);
+
+ /**
+ * Test V2 client to V1 server - success scenario.
+ * V2 client should auto-detect V1 server response and handle it correctly.
+ */
+ @Test
+ public void testServerV1ToClientV2Success() throws Exception {
+ // Manual V1 server construction (simulates legacy server)
+ startV1Server(8121);
+ // Use production-like V2 client (NettyClientBootstrap)
+ connectV2Client("127.0.0.1", 8121, 3000);
+
+ // Step 1: Register TM
+ RegisterTMResponse tmResponse = doSendRegister(null);
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified");
+
+ // Step 2: Send heartbeat to verify bidirectional encode/decode
+ doSendHeartbeatAndVerify();
+ LOGGER.info("Heartbeat PING/PONG verified - bidirectional
communication works (V1 Server + V2 Client)");
+ }
+
+ /**
+ * Test V2 client to V1 server - failure scenario (auth error).
+ * Note: V1 server does not include error message in response.
+ */
+ @Test
+ public void testServerV1ToClientV2AuthFailure() throws Exception {
+ startV1Server(8122);
+ connectV2Client("127.0.0.1", 8122, 3000);
+
+ RegisterTMResponse tmResponse =
doSendRegister(CodecTestCheckAuthHandler.CODEC_TEST_REG_ERROR);
+ Assertions.assertFalse(tmResponse.isIdentified(), "Should fail
identification due to auth error");
+ Assertions.assertTrue(StringUtils.isBlank(tmResponse.getMsg()), "V1
server should not include error message");
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
new file mode 100644
index 0000000000..bdd8153e2c
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV1Test.java
@@ -0,0 +1,68 @@
+/*
+ * 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.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V2 Server with V1 Client compatibility.
+ * - Server: MockNettyRemotingServer (production V2 server with
MultiProtocolDecoder)
+ * - Client: Manual V1 construction (simulates legacy client with
ProtocolEncoderV1)
+ *
+ * This tests server backward compatibility - V2 server handling V1 client.
+ */
+public class ServerV2ToClientV1Test extends MultiVersionCompatibilityTest {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ServerV2ToClientV1Test.class);
+
+ /**
+ * Test V1 client to V2 server - success scenario.
+ * V2 server should handle V1 client request and respond appropriately.
+ */
+ @Test
+ public void testServerV2ToClientV1Success() throws Exception {
+ // Use production-like V2 server (MockNettyRemotingServer)
+ startV2Server(8211);
+ // Use manual V1 client construction (simulates legacy client)
+ connectV1Client("127.0.0.1", 8211, 3000);
+
+ // Step 1: Register TM
+ RegisterTMResponse tmResponse = doSendRegister(null);
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified");
+
+ // Step 2: Send heartbeat to verify bidirectional encode/decode
+ doSendHeartbeatAndVerify();
+ LOGGER.info("Heartbeat PING/PONG verified - bidirectional
communication works (V2 Server + V1 Client)");
+ }
+
+ /**
+ * Test V1 client to V2 server - second success scenario.
+ * MockRegisterProcessor always returns success, so we test another
success case.
+ */
+ @Test
+ public void testServerV2ToClientV1SuccessWithExtraData() throws Exception {
+ startV2Server(8212);
+ connectV1Client("127.0.0.1", 8212, 3000);
+
+ RegisterTMResponse tmResponse = doSendRegister("test-extra-data");
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified with extra data");
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
new file mode 100644
index 0000000000..f618ea5d48
--- /dev/null
+++
b/test-suite/test-new-version/src/test/java/org/apache/seata/core/rpc/netty/multiversion/ServerV2ToClientV2Test.java
@@ -0,0 +1,72 @@
+/*
+ * 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.seata.core.rpc.netty.multiversion;
+
+import org.apache.seata.core.protocol.RegisterTMResponse;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Test V2 Server with V2 Client compatibility.
+ * Both server and client use V2 protocol with production-like bootstrap:
+ * - Server: MockNettyRemotingServer (uses ProtocolDetectHandler ->
SeataDetector -> MultiProtocolDecoder)
+ * - Client: NettyClientBootstrap (uses ProtocolEncoderV2 +
MultiProtocolDecoder)
+ *
+ * This is the most production-like test scenario.
+ */
+public class ServerV2ToClientV2Test extends MultiVersionCompatibilityTest {
+
+ private static final Logger LOGGER =
LoggerFactory.getLogger(ServerV2ToClientV2Test.class);
+
+ /**
+ * Test V2 client to V2 server - success scenario.
+ * Uses production MockNettyRemotingServer and NettyClientBootstrap.
+ */
+ @Test
+ public void testServerV2ToClientV2Success() throws Exception {
+ // Use production-like V2 server (MockNettyRemotingServer)
+ startV2Server(8221);
+ // Use production-like V2 client (NettyClientBootstrap)
+ connectV2Client("127.0.0.1", 8221, 3000);
+
+ // Step 1: Register TM
+ RegisterTMResponse tmResponse = doSendRegister(null);
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified");
+
+ // Step 2: Send heartbeat to verify bidirectional encode/decode
+ doSendHeartbeatAndVerify();
+ LOGGER.info("Heartbeat PING/PONG verified - bidirectional
communication works (V2 Server + V2 Client)");
+ }
+
+ /**
+ * Test V2 client to V2 server - failure scenario (auth error).
+ * Note: V2 protocol includes error message in response.
+ */
+ @Test
+ public void testServerV2ToClientV2AuthFailure() throws Exception {
+ startV2Server(8222);
+ connectV2Client("127.0.0.1", 8222, 3000);
+
+ // MockRegisterProcessor in MockNettyRemotingServer always returns
success,
+ // so we only test the success path for V2 server.
+ // Auth failure testing is done with manual V1 server construction.
+ RegisterTMResponse tmResponse = doSendRegister(null);
+ Assertions.assertTrue(tmResponse.isIdentified(), "Should be
successfully identified");
+ }
+}
diff --git
a/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
b/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
new file mode 100644
index 0000000000..c5d514453e
--- /dev/null
+++
b/test-suite/test-new-version/src/test/resources/META-INF/services/org.apache.seata.core.rpc.RegisterCheckAuthHandler
@@ -0,0 +1,17 @@
+#
+# 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.
+#
+org.apache.seata.core.rpc.netty.CodecTestCheckAuthHandler
\ No newline at end of file
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]