|
8 | 8 | #include "rmw/qos_profiles.h" |
9 | 9 | #include "rosidl_typesupport_cpp/message_type_support.hpp" |
10 | 10 |
|
| 11 | +#include <chrono> |
| 12 | +#include <thread> |
| 13 | +#include <vector> |
| 14 | + |
| 15 | +#include <unistd.h> |
| 16 | + |
| 17 | +#include "../src/types.hpp" |
| 18 | +#include "../src/transport.hpp" |
| 19 | +#include "../src/serialization.hpp" |
| 20 | + |
11 | 21 | class PubSubTest : public RmwUdsNodeTest |
12 | 22 | { |
13 | 23 | protected: |
@@ -200,3 +210,80 @@ TEST_F(PubSubTest, StringMessages) |
200 | 210 | ASSERT_TRUE(taken); |
201 | 211 | EXPECT_EQ("Hello from Unix sockets!", recv_msg.string_value); |
202 | 212 | } |
| 213 | + |
| 214 | +// Regression for the rmw_take_sequence cursor bug: a deserialize failure in the |
| 215 | +// middle of a batch must not leave an uninitialized hole or miscount size — each |
| 216 | +// success is written at the *taken cursor, not the loop index. We inject |
| 217 | +// [good, corrupt, good] straight into the subscription's datagram socket; a |
| 218 | +// single sender over SOCK_DGRAM preserves datagram order, so the corrupt one |
| 219 | +// lands between the two good ones. Before the fix the second good message was |
| 220 | +// written at data[2] and lost, while size == *taken == 2 exposed data[1] (the |
| 221 | +// hole left by the failed deserialize) to the consumer as a valid message. |
| 222 | +TEST_F(PubSubTest, TakeSequenceSkipsMidBatchCorruptContiguously) |
| 223 | +{ |
| 224 | + auto sub_opts = rmw_get_default_subscription_options(); |
| 225 | + sub = rmw_create_subscription(node, ts, "/take_seq_corrupt", &qos, &sub_opts); |
| 226 | + ASSERT_NE(nullptr, sub); |
| 227 | + |
| 228 | + auto * sub_data = static_cast<rmw_uds::UdsSubscription *>(sub->data); |
| 229 | + ASSERT_FALSE(sub_data->socket_path.empty()); |
| 230 | + ASSERT_NE(nullptr, sub_data->callbacks); |
| 231 | + |
| 232 | + // A valid serialized BasicTypes payload (deserializes successfully). |
| 233 | + test_msgs::msg::BasicTypes good; |
| 234 | + good.int32_value = 4242; |
| 235 | + good.bool_value = true; |
| 236 | + std::vector<uint8_t> good_payload; |
| 237 | + ASSERT_TRUE(rmw_uds::serialize(&good, sub_data->callbacks, good_payload)); |
| 238 | + |
| 239 | + int send_fd = rmw_uds::create_send_socket(); |
| 240 | + ASSERT_GE(send_fd, 0); |
| 241 | + |
| 242 | + auto inject = [&](const uint8_t * data, size_t len) { |
| 243 | + rmw_uds::WireHeader hdr; |
| 244 | + std::memset(&hdr, 0, sizeof(hdr)); |
| 245 | + hdr.payload_size = static_cast<uint32_t>(len); |
| 246 | + hdr.msg_type = 0; // topic message |
| 247 | + ASSERT_TRUE(rmw_uds::send_to(send_fd, sub_data->socket_path, hdr, data, len)); |
| 248 | + }; |
| 249 | + |
| 250 | + // good, corrupt (far too short to deserialize as BasicTypes), good. |
| 251 | + const uint8_t corrupt[4] = {0xDE, 0xAD, 0xBE, 0xEF}; |
| 252 | + inject(good_payload.data(), good_payload.size()); |
| 253 | + inject(corrupt, sizeof(corrupt)); |
| 254 | + inject(good_payload.data(), good_payload.size()); |
| 255 | + |
| 256 | + // Datagrams are buffered by the kernel on send; a short pause is defensive. |
| 257 | + std::this_thread::sleep_for(std::chrono::milliseconds(50)); |
| 258 | + |
| 259 | + // Take all three at once: the corrupt one is skipped, leaving two contiguous |
| 260 | + // good messages with size == *taken == 2 (no hole, nothing lost). |
| 261 | + test_msgs::msg::BasicTypes out[3]; |
| 262 | + void * out_ptrs[3] = {&out[0], &out[1], &out[2]}; |
| 263 | + rmw_message_sequence_t seq; |
| 264 | + seq.data = out_ptrs; |
| 265 | + seq.size = 0; |
| 266 | + seq.capacity = 3; |
| 267 | + seq.allocator = nullptr; |
| 268 | + |
| 269 | + rmw_message_info_t infos[3]; |
| 270 | + std::memset(infos, 0, sizeof(infos)); |
| 271 | + rmw_message_info_sequence_t info_seq; |
| 272 | + info_seq.data = infos; |
| 273 | + info_seq.size = 0; |
| 274 | + info_seq.capacity = 3; |
| 275 | + info_seq.allocator = nullptr; |
| 276 | + |
| 277 | + size_t taken = 99; |
| 278 | + EXPECT_EQ( |
| 279 | + RMW_RET_OK, |
| 280 | + rmw_take_sequence(sub, 3, &seq, &info_seq, &taken, nullptr)); |
| 281 | + |
| 282 | + EXPECT_EQ(2u, taken); |
| 283 | + EXPECT_EQ(2u, seq.size); |
| 284 | + EXPECT_EQ(2u, info_seq.size); |
| 285 | + EXPECT_EQ(4242, out[0].int32_value); |
| 286 | + EXPECT_EQ(4242, out[1].int32_value); // the bug left this a hole and lost it |
| 287 | + |
| 288 | + close(send_fd); |
| 289 | +} |
0 commit comments