Add unit test for remoting protocol
diff --git a/src/main/cpp/remoting/BUILD.bazel b/src/main/cpp/remoting/BUILD.bazel
index 108f8ce..ae84d43 100644
--- a/src/main/cpp/remoting/BUILD.bazel
+++ b/src/main/cpp/remoting/BUILD.bazel
@@ -11,5 +11,6 @@
         "@asio//:asio",
         "@com_google_protobuf//:protobuf",
         "@com_google_absl//absl/memory",
+        "@com_google_absl//absl/container:flat_hash_map",
     ]
 )
\ No newline at end of file
diff --git a/src/main/cpp/remoting/BrokerData.cpp b/src/main/cpp/remoting/BrokerData.cpp
new file mode 100644
index 0000000..cadba72
--- /dev/null
+++ b/src/main/cpp/remoting/BrokerData.cpp
@@ -0,0 +1,27 @@
+#include "BrokerData.h"
+#include <string>
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+BrokerData BrokerData::decode(const google::protobuf::Struct& root) {
+  BrokerData broker_data;
+  auto fields = root.fields();
+  if (fields.contains("cluster")) {
+    broker_data.cluster_ = fields["cluster"].string_value();
+  }
+
+  if (fields.contains("brokerName")) {
+    broker_data.broker_name_ = fields["brokerName"].string_value();
+  }
+
+  if (fields.contains("brokerAddrs")) {
+    auto items = fields["brokerAddrs"].struct_value().fields();
+    for (const auto& item : items) {
+      auto k = std::stoll(item.first);
+      broker_data.broker_addresses_.insert({k, item.second.string_value()});
+    }
+  }
+  return broker_data;
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/remoting/QueueData.cpp b/src/main/cpp/remoting/QueueData.cpp
new file mode 100644
index 0000000..31a5702
--- /dev/null
+++ b/src/main/cpp/remoting/QueueData.cpp
@@ -0,0 +1,33 @@
+#include "QueueData.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+QueueData QueueData::decode(const google::protobuf::Struct& root) {
+  auto fields = root.fields();
+
+  QueueData queue_data;
+
+  if (fields.contains("brokerName")) {
+    queue_data.broker_name_ = fields["brokerName"].string_value();
+  }
+
+  if (fields.contains("readQueueNums")) {
+    queue_data.read_queue_number_ = fields["readQueueNums"].number_value();
+  }
+
+  if (fields.contains("writeQueueNums")) {
+    queue_data.write_queue_number_ = fields["writeQueueNums"].number_value();
+  }
+
+  if (fields.contains("perm")) {
+    queue_data.perm_ = fields["perm"].number_value();
+  }
+
+  if (fields.contains("topicSynFlag")) {
+    queue_data.topic_system_flag_ = fields["topicSynFlag"].number_value();
+  }
+
+  return queue_data;
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/remoting/TopicRouteData.cpp b/src/main/cpp/remoting/TopicRouteData.cpp
new file mode 100644
index 0000000..4414135
--- /dev/null
+++ b/src/main/cpp/remoting/TopicRouteData.cpp
@@ -0,0 +1,30 @@
+#include "TopicRouteData.h"
+#include "BrokerData.h"
+#include "rocketmq/RocketMQ.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+TopicRouteData TopicRouteData::decode(const google::protobuf::Struct& root) {
+  auto fields = root.fields();
+
+  TopicRouteData topic_route_data;
+
+  if (fields.contains("queueDatas")) {
+    auto queue_data_list = fields.at("queueDatas");
+
+    for (auto& item : queue_data_list.list_value().values()) {
+      topic_route_data.queue_data_.push_back(QueueData::decode(item.struct_value()));
+    }
+  }
+
+  if (fields.contains("brokerDatas")) {
+    auto broker_data_list = fields.at("brokerDatas");
+    for (auto& item : broker_data_list.list_value().values()) {
+      topic_route_data.broker_data_.push_back(BrokerData::decode(item.struct_value()));
+    }
+  }
+
+  return topic_route_data;
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/remoting/include/BrokerData.h b/src/main/cpp/remoting/include/BrokerData.h
new file mode 100644
index 0000000..d8d43d0
--- /dev/null
+++ b/src/main/cpp/remoting/include/BrokerData.h
@@ -0,0 +1,22 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+#include "absl/container/flat_hash_map.h"
+#include "google/protobuf/struct.pb.h"
+#include "google/protobuf/util/json_util.h"
+
+#include "rocketmq/RocketMQ.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+struct BrokerData {
+  std::string cluster_;
+  std::string broker_name_;
+  absl::flat_hash_map<std::int64_t, std::string> broker_addresses_;
+
+  static BrokerData decode(const google::protobuf::Struct& root);
+};
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/remoting/include/QueueData.h b/src/main/cpp/remoting/include/QueueData.h
new file mode 100644
index 0000000..6fb5c7f
--- /dev/null
+++ b/src/main/cpp/remoting/include/QueueData.h
@@ -0,0 +1,28 @@
+#pragma once
+
+#include <cstdint>
+#include <string>
+
+#include "google/protobuf/struct.pb.h"
+#include "google/protobuf/util/json_util.h"
+
+#include "rocketmq/RocketMQ.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+struct QueueData {
+  std::string broker_name_;
+  std::int32_t read_queue_number_{0};
+  std::int32_t write_queue_number_{0};
+  std::uint32_t perm_{0};
+
+  /**
+   * @brief in Java, it's named "topicSynFlag"
+   *
+   */
+  std::uint32_t topic_system_flag_{0};
+
+  static QueueData decode(const google::protobuf::Struct& root);
+};
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/main/cpp/remoting/include/TopicRouteData.h b/src/main/cpp/remoting/include/TopicRouteData.h
new file mode 100644
index 0000000..9e45e3a
--- /dev/null
+++ b/src/main/cpp/remoting/include/TopicRouteData.h
@@ -0,0 +1,29 @@
+#pragma once
+
+#include <vector>
+
+#include "BrokerData.h"
+#include "QueueData.h"
+#include "rocketmq/RocketMQ.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+struct TopicRouteData {
+
+  /**
+   * @brief In Java, it's named "queueDatas"
+   *
+   */
+  std::vector<QueueData> queue_data_;
+
+  /**
+   * @brief In Java, it's named "brokerDatas"
+   *
+   */
+  std::vector<BrokerData> broker_data_;
+
+  
+  static TopicRouteData decode(const google::protobuf::Struct& root);
+};
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/test/cpp/ut/remoting/BUILD.bazel b/src/test/cpp/ut/remoting/BUILD.bazel
index 394a8f8..c0dacd1 100644
--- a/src/test/cpp/ut/remoting/BUILD.bazel
+++ b/src/test/cpp/ut/remoting/BUILD.bazel
@@ -20,4 +20,38 @@
         "//src/main/cpp/remoting",
         "@com_google_googletest//:gtest_main",
     ],
+)
+
+cc_test(
+    name = "broker_data_test",
+    srcs = [
+        "BrokerDataTest.cpp",
+    ],
+    deps = [
+        "//src/main/cpp/remoting",
+        "@com_google_googletest//:gtest_main",
+    ]
+)
+
+
+cc_test(
+    name = "queue_data_test",
+    srcs = [
+        "QueueDataTest.cpp",
+    ],
+    deps = [
+        "//src/main/cpp/remoting",
+        "@com_google_googletest//:gtest_main",
+    ]
+)
+
+cc_test(
+    name = "topic_route_data_test",
+    srcs = [
+        "TopicRouteDataTest.cpp",
+    ],
+    deps = [
+        "//src/main/cpp/remoting",
+        "@com_google_googletest//:gtest_main",
+    ]
 )
\ No newline at end of file
diff --git a/src/test/cpp/ut/remoting/BrokerDataTest.cpp b/src/test/cpp/ut/remoting/BrokerDataTest.cpp
new file mode 100644
index 0000000..168203c
--- /dev/null
+++ b/src/test/cpp/ut/remoting/BrokerDataTest.cpp
@@ -0,0 +1,28 @@
+#include "BrokerData.h"
+
+#include <iostream>
+
+#include "rocketmq/RocketMQ.h"
+#include "gtest/gtest.h"
+
+#include "google/protobuf/struct.pb.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+TEST(BrokerDataTest, testDecode) {
+  std::string json =
+      R"({"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false})";
+  google::protobuf::Struct root;
+
+  auto status = google::protobuf::util::JsonStringToMessage(json, &root);
+  ASSERT_TRUE(status.ok());
+
+  BrokerData&& broker_data = BrokerData::decode(root);
+  EXPECT_EQ("b1", broker_data.broker_name_);
+  EXPECT_EQ("cluster", broker_data.cluster_);
+  EXPECT_EQ(2, broker_data.broker_addresses_.size());
+  EXPECT_EQ("abc", broker_data.broker_addresses_.at(1));
+  EXPECT_EQ("def", broker_data.broker_addresses_.at(2));
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/test/cpp/ut/remoting/QueueDataTest.cpp b/src/test/cpp/ut/remoting/QueueDataTest.cpp
new file mode 100644
index 0000000..936a688
--- /dev/null
+++ b/src/test/cpp/ut/remoting/QueueDataTest.cpp
@@ -0,0 +1,22 @@
+#include "gtest/gtest.h"
+
+#include "QueueData.h"
+#include "rocketmq/RocketMQ.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+TEST(QueueDataTest, testDecode) {
+  std::string json = R"({"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8})";
+  google::protobuf::Struct root;
+  auto status = google::protobuf::util::JsonStringToMessage(json, &root);
+  EXPECT_TRUE(status.ok());
+
+  QueueData queue_data = QueueData::decode(root);
+  EXPECT_EQ("broker1", queue_data.broker_name_);
+  EXPECT_EQ(6, queue_data.perm_);
+  EXPECT_EQ(4, queue_data.read_queue_number_);
+  EXPECT_EQ(8, queue_data.write_queue_number_);
+  EXPECT_EQ(3, queue_data.topic_system_flag_);
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file
diff --git a/src/test/cpp/ut/remoting/TopicRouteDataTest.cpp b/src/test/cpp/ut/remoting/TopicRouteDataTest.cpp
new file mode 100644
index 0000000..3a11d87
--- /dev/null
+++ b/src/test/cpp/ut/remoting/TopicRouteDataTest.cpp
@@ -0,0 +1,20 @@
+#include "TopicRouteData.h"
+#include "rocketmq/RocketMQ.h"
+#include "gtest/gtest.h"
+
+ROCKETMQ_NAMESPACE_BEGIN
+
+TEST(TopicRouteDataTest, testDecode) {
+  std::string json =
+      R"({"brokerDatas":[{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false},{"brokerAddrs":{"1":"abc","2":"def"},"brokerName":"b1","cluster":"cluster","enableActingMaster":false}],"filterServerTable":{},"queueDatas":[{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8},{"brokerName":"broker1","perm":6,"readQueueNums":4,"topicSynFlag":3,"writeQueueNums":8}]})";
+  google::protobuf::Struct root;
+  auto status = google::protobuf::util::JsonStringToMessage(json, &root);
+  EXPECT_TRUE(status.ok());
+
+  auto topic_route_data = TopicRouteData::decode(root);
+
+  EXPECT_FALSE(topic_route_data.broker_data_.empty());
+  EXPECT_FALSE(topic_route_data.queue_data_.empty());
+}
+
+ROCKETMQ_NAMESPACE_END
\ No newline at end of file