• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mavlink / MAVSDK / 22337580839

24 Feb 2026 05:11AM UTC coverage: 48.961% (-0.02%) from 48.982%
22337580839

Pull #2775

github

web-flow
Merge 2f0d03e12 into b46b0e529
Pull Request #2775: Add devcontainer

18351 of 37481 relevant lines covered (48.96%)

668.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.15
/cpp/src/system_tests/intercept.cpp
1
#include "log.h"
2
#include "mavsdk.h"
3
#include "plugins/telemetry/telemetry.h"
4
#include "plugins/telemetry_server/telemetry_server.h"
5
#include <chrono>
6
#include <thread>
7
#include <future>
8
#include <gtest/gtest.h>
9
#include <json/json.h>
10

11
using namespace mavsdk;
12

13
TEST(SystemTest, InterceptIncomingModifyLocal)
4 ✔
14
{
15
    // Create 3 MAVSDK instances: Sender -> Interceptor/Forwarder -> Receiver
16
    Mavsdk mavsdk_sender{Mavsdk::Configuration{ComponentType::Autopilot}};
1 ✔
17
    Mavsdk mavsdk_interceptor{Mavsdk::Configuration{ComponentType::GroundStation}};
1 ✔
18
    Mavsdk mavsdk_receiver{Mavsdk::Configuration{ComponentType::GroundStation}};
1 ✔
19

20
    // Set up connections with forwarding enabled on interceptor
21
    ASSERT_EQ(
1 ✔
22
        mavsdk_sender.add_any_connection("udpout://127.0.0.1:17000"), ConnectionResult::Success);
1 ✔
23
    ASSERT_EQ(
1 ✔
24
        mavsdk_interceptor.add_any_connection(
25
            "udpin://0.0.0.0:17000", ForwardingOption::ForwardingOn),
26
        ConnectionResult::Success);
1 ✔
27
    ASSERT_EQ(
1 ✔
28
        mavsdk_interceptor.add_any_connection(
29
            "udpout://127.0.0.1:17001", ForwardingOption::ForwardingOn),
30
        ConnectionResult::Success);
1 ✔
31
    ASSERT_EQ(
1 ✔
32
        mavsdk_receiver.add_any_connection("udpin://0.0.0.0:17001"), ConnectionResult::Success);
1 ✔
33

34
    // Wait for connections to establish
35
    LogInfo() << "Waiting for connections to establish...";
1 ✔
36

37
    // Interceptor discovers sender
38
    auto sender_system_opt = mavsdk_interceptor.first_autopilot(10.0);
1 ✔
39
    ASSERT_TRUE(sender_system_opt);
1 ✔
40
    auto sender_system = sender_system_opt.value();
1 ✔
41
    ASSERT_TRUE(sender_system->has_autopilot());
1 ✔
42

43
    // Receiver discovers sender (via forwarding)
44
    auto sender_system_receiver_opt = mavsdk_receiver.first_autopilot(10.0);
1 ✔
45
    ASSERT_TRUE(sender_system_receiver_opt);
1 ✔
46
    auto sender_system_receiver = sender_system_receiver_opt.value();
1 ✔
47
    ASSERT_TRUE(sender_system_receiver->has_autopilot());
1 ✔
48

49
    // Wait for sender to discover interceptor connection
50
    LogInfo() << "Waiting for sender to connect to interceptor...";
1 ✔
51
    while (mavsdk_sender.systems().size() == 0) {
2 ✔
52
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1 ✔
53
    }
54
    auto interceptor_system = mavsdk_sender.systems().at(0);
1 ✔
55

56
    // Create telemetry instances
57
    auto telemetry_interceptor = Telemetry{sender_system};
1 ✔
58
    auto telemetry_receiver = Telemetry{sender_system_receiver};
1 ✔
59
    auto telemetry_server = TelemetryServer{mavsdk_sender.server_component()};
1 ✔
60

61
    // Original position values
62
    const double original_lat = 47.3977421; // Zurich coordinates
1 ✔
63
    const double original_lon = 8.5455938;
1 ✔
64
    const float original_alt = 488.0f;
1 ✔
65

66
    // Modified position values (what intercept will change to)
67
    const double modified_lat = 37.7749295; // San Francisco coordinates
1 ✔
68
    const double modified_lon = -122.4194155;
1 ✔
69

70
    // Set up raw mavlink intercept callback on interceptor to modify incoming GLOBAL_POSITION_INT
71
    std::atomic<bool> intercept_called{false};
1 ✔
72

73
    mavsdk_interceptor.intercept_incoming_messages_async([&](mavlink_message_t& message) -> bool {
7 ✔
74
        if (message.msgid == MAVLINK_MSG_ID_GLOBAL_POSITION_INT) {
6 ✔
75
            LogInfo() << "Intercepting GLOBAL_POSITION_INT message from system "
1 ✔
76
                      << (int)message.sysid;
1 ✔
77

78
            // Decode the message
79
            mavlink_global_position_int_t pos;
80
            mavlink_msg_global_position_int_decode(&message, &pos);
1 ✔
81

82
            LogInfo() << "Original coordinates: lat=" << (pos.lat / 1e7)
1 ✔
83
                      << ", lon=" << (pos.lon / 1e7);
1 ✔
84

85
            // Modify the coordinates to San Francisco
86
            pos.lat = static_cast<int32_t>(modified_lat * 1e7);
1 ✔
87
            pos.lon = static_cast<int32_t>(modified_lon * 1e7);
1 ✔
88

89
            // Re-encode the modified message using a dedicated channel to avoid
90
            // racing with ServerComponentImpl::queue_message on channel 0.
91
            mavlink_msg_global_position_int_encode_chan(
1 ✔
92
                message.sysid, message.compid, MAVLINK_COMM_NUM_BUFFERS - 1, &message, &pos);
1 ✔
93

94
            LogInfo() << "Modified coordinates to San Francisco: lat=" << (pos.lat / 1e7)
1 ✔
95
                      << ", lon=" << (pos.lon / 1e7);
1 ✔
96

97
            intercept_called = true;
1 ✔
98
        }
99
        return true; // Keep the message
6 ✔
100
    });
101

102
    // Promise/future for interceptor telemetry
103
    auto interceptor_prom = std::promise<Telemetry::Position>();
1 ✔
104
    auto interceptor_fut = interceptor_prom.get_future();
1 ✔
105

106
    // Promise/future for receiver telemetry
107
    auto receiver_prom = std::promise<Telemetry::Position>();
1 ✔
108
    auto receiver_fut = receiver_prom.get_future();
1 ✔
109

110
    // Subscribe to position updates on interceptor
111
    auto interceptor_handle =
112
        telemetry_interceptor.subscribe_position([&interceptor_prom](Telemetry::Position position) {
3 ✔
113
            LogInfo() << "Interceptor received position: lat=" << position.latitude_deg
2 ✔
114
                      << ", lon=" << position.longitude_deg;
1 ✔
115
            interceptor_prom.set_value(position);
1 ✔
116
        });
1 ✔
117

118
    // Subscribe to position updates on receiver
119
    auto receiver_handle =
120
        telemetry_receiver.subscribe_position([&receiver_prom](Telemetry::Position position) {
3 ✔
121
            LogInfo() << "Receiver received position: lat=" << position.latitude_deg
2 ✔
122
                      << ", lon=" << position.longitude_deg;
1 ✔
123
            receiver_prom.set_value(position);
1 ✔
124
        });
1 ✔
125

126
    // Send position from sender
127
    LogInfo() << "Publishing original position from sender...";
1 ✔
128
    TelemetryServer::Position position{};
1 ✔
129
    position.latitude_deg = original_lat;
1 ✔
130
    position.longitude_deg = original_lon;
1 ✔
131
    position.absolute_altitude_m = original_alt;
1 ✔
132
    position.relative_altitude_m = original_alt;
1 ✔
133

134
    // Empty velocity and heading for this test
135
    TelemetryServer::VelocityNed velocity{};
1 ✔
136
    TelemetryServer::Heading heading{};
1 ✔
137

138
    ASSERT_EQ(
1 ✔
139
        telemetry_server.publish_position(position, velocity, heading),
140
        TelemetryServer::Result::Success);
1 ✔
141

142
    // Wait for both telemetry callbacks
143
    ASSERT_EQ(interceptor_fut.wait_for(std::chrono::seconds(10)), std::future_status::ready);
1 ✔
144
    ASSERT_EQ(receiver_fut.wait_for(std::chrono::seconds(10)), std::future_status::ready);
1 ✔
145

146
    // Give intercept callback time to complete
147
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
1 ✔
148

149
    // Verify intercept was called
150
    ASSERT_TRUE(intercept_called.load());
1 ✔
151

152
    // Get the results
153
    auto interceptor_position = interceptor_fut.get();
1 ✔
154
    auto receiver_position = receiver_fut.get();
1 ✔
155

156
    // Verify interceptor sees modified coordinates (San Francisco)
157
    EXPECT_NEAR(interceptor_position.latitude_deg, modified_lat, 1e-6);
1 ✔
158
    EXPECT_NEAR(interceptor_position.longitude_deg, modified_lon, 1e-6);
1 ✔
159
    EXPECT_NEAR(interceptor_position.absolute_altitude_m, original_alt, 1.0f);
1 ✔
160

161
    // Receiver also gets modified coordinates because intercept happens BEFORE forwarding
162
    EXPECT_NEAR(
1 ✔
163
        receiver_position.latitude_deg,
164
        modified_lat,
165
        1e-6); // Should be modified San Francisco coordinates
1 ✔
166
    EXPECT_NEAR(
1 ✔
167
        receiver_position.longitude_deg,
168
        modified_lon,
169
        1e-6); // Should be modified San Francisco coordinates
1 ✔
170
    EXPECT_NEAR(receiver_position.absolute_altitude_m, original_alt, 1.0f);
1 ✔
171

172
    LogInfo() << "Test completed successfully:";
1 ✔
173
    LogInfo() << "  - Interceptor saw modified coordinates (San Francisco)";
1 ✔
174
    LogInfo() << "  - Receiver also saw modified coordinates (San Francisco)";
1 ✔
175
    LogInfo() << "  - This proves intercept affects BOTH local processing AND forwarding";
1 ✔
176

177
    // Cleanup
178
    telemetry_interceptor.unsubscribe_position(interceptor_handle);
1 ✔
179
    telemetry_receiver.unsubscribe_position(receiver_handle);
1 ✔
180
    mavsdk_interceptor.intercept_incoming_messages_async(nullptr);
1 ✔
181
}
1 ✔
182

183
TEST(SystemTest, InterceptJsonIncoming)
4 ✔
184
{
185
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1 ✔
186
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1 ✔
187

188
    ASSERT_EQ(
1 ✔
189
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17000"),
190
        ConnectionResult::Success);
1 ✔
191
    ASSERT_EQ(
1 ✔
192
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17000"), ConnectionResult::Success);
1 ✔
193

194
    // Ground station discovers the autopilot system
195
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1 ✔
196
    ASSERT_TRUE(maybe_system);
1 ✔
197
    auto system = maybe_system.value();
1 ✔
198
    ASSERT_TRUE(system->has_autopilot());
1 ✔
199

200
    // Wait for autopilot instance to discover the connection to the ground station
201
    LogInfo() << "Waiting for autopilot system to connect...";
1 ✔
202
    while (mavsdk_autopilot.systems().size() == 0) {
2 ✔
203
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1 ✔
204
    }
205

206
    // Create telemetry instances
207
    auto telemetry = Telemetry{system};
1 ✔
208
    auto telemetry_server = TelemetryServer{mavsdk_autopilot.server_component()};
1 ✔
209

210
    // Set up incoming JSON message interception on ground station
211
    auto json_prom = std::promise<Mavsdk::MavlinkMessage>();
1 ✔
212
    auto json_fut = json_prom.get_future();
1 ✔
213
    std::atomic<bool> intercept_called{false};
1 ✔
214

215
    auto json_handle = mavsdk_groundstation.subscribe_incoming_messages_json(
1 ✔
216
        [&json_prom, &intercept_called](Mavsdk::MavlinkMessage json_message) {
3 ✔
217
            LogInfo() << "Intercepted incoming JSON message: " << json_message.message_name
2 ✔
218
                      << " from system " << json_message.system_id
1 ✔
219
                      << " with fields: " << json_message.fields_json;
1 ✔
220

221
            if (json_message.message_name == "GLOBAL_POSITION_INT") {
1 ✔
222
                intercept_called = true;
1 ✔
223
                json_prom.set_value(json_message);
1 ✔
224
                return true; // Don't drop the message
1 ✔
225
            }
226
            return true; // Let other messages through
×
227
        });
228

229
    // Publish position from autopilot
230
    LogInfo() << "Publishing position from autopilot...";
1 ✔
231
    TelemetryServer::Position position{};
1 ✔
232
    position.latitude_deg = 47.3977421; // Zurich coordinates
1 ✔
233
    position.longitude_deg = 8.5455938;
1 ✔
234
    position.absolute_altitude_m = 488.0f;
1 ✔
235
    position.relative_altitude_m = 488.0f;
1 ✔
236

237
    TelemetryServer::VelocityNed velocity{};
1 ✔
238
    velocity.north_m_s = 1.5f;
1 ✔
239
    velocity.east_m_s = 2.0f;
1 ✔
240
    velocity.down_m_s = -0.5f;
1 ✔
241

242
    TelemetryServer::Heading heading{};
1 ✔
243
    heading.heading_deg = 180.0f;
1 ✔
244

245
    ASSERT_EQ(
1 ✔
246
        telemetry_server.publish_position(position, velocity, heading),
247
        TelemetryServer::Result::Success);
1 ✔
248

249
    // Wait for JSON interception to occur
250
    ASSERT_EQ(json_fut.wait_for(std::chrono::seconds(1)), std::future_status::ready);
1 ✔
251

252
    // Verify intercept was called
253
    ASSERT_TRUE(intercept_called.load());
1 ✔
254

255
    auto intercepted_message = json_fut.get();
1 ✔
256

257
    // Verify the intercepted JSON message structure
258
    EXPECT_EQ(intercepted_message.message_name, "GLOBAL_POSITION_INT");
1 ✔
259
    EXPECT_EQ(intercepted_message.system_id, 1u); // Autopilot system ID
1 ✔
260
    EXPECT_GT(intercepted_message.fields_json.length(), 0u);
1 ✔
261

262
    // Parse JSON to verify field values
263
    Json::Value json;
1 ✔
264
    Json::Reader reader;
1 ✔
265
    ASSERT_TRUE(reader.parse(intercepted_message.fields_json, json));
1 ✔
266

267
    // Verify position fields from the intercepted message
268
    EXPECT_NEAR(json["lat"].asInt() / 1e7, position.latitude_deg, 1e-6);
1 ✔
269
    EXPECT_NEAR(json["lon"].asInt() / 1e7, position.longitude_deg, 1e-6);
1 ✔
270
    EXPECT_NEAR(json["alt"].asInt() / 1e3, position.absolute_altitude_m, 1.0);
1 ✔
271
    EXPECT_NEAR(json["relative_alt"].asInt() / 1e3, position.relative_altitude_m, 1.0);
1 ✔
272

273
    // Verify velocity fields
274
    EXPECT_NEAR(json["vx"].asInt() / 1e2, velocity.north_m_s, 0.1);
1 ✔
275
    EXPECT_NEAR(json["vy"].asInt() / 1e2, velocity.east_m_s, 0.1);
1 ✔
276
    EXPECT_NEAR(json["vz"].asInt() / 1e2, velocity.down_m_s, 0.1);
1 ✔
277

278
    // Verify heading field
279
    EXPECT_NEAR(json["hdg"].asInt() / 1e2, heading.heading_deg, 1.0);
1 ✔
280

281
    LogInfo() << "Successfully tested incoming JSON message interception";
1 ✔
282
    LogInfo() << "  - Message name: " << intercepted_message.message_name;
1 ✔
283
    LogInfo() << "  - System ID: " << intercepted_message.system_id;
1 ✔
284
    LogInfo() << "  - Fields JSON length: " << intercepted_message.fields_json.length();
1 ✔
285

286
    // Cleanup
287
    mavsdk_groundstation.unsubscribe_incoming_messages_json(json_handle);
1 ✔
288
}
1 ✔
289

290
TEST(SystemTest, InterceptJsonOutgoing)
4 ✔
291
{
292
    Mavsdk mavsdk_groundstation{Mavsdk::Configuration{ComponentType::GroundStation}};
1 ✔
293
    Mavsdk mavsdk_autopilot{Mavsdk::Configuration{ComponentType::Autopilot}};
1 ✔
294

295
    ASSERT_EQ(
1 ✔
296
        mavsdk_groundstation.add_any_connection("udpin://0.0.0.0:17000"),
297
        ConnectionResult::Success);
1 ✔
298
    ASSERT_EQ(
1 ✔
299
        mavsdk_autopilot.add_any_connection("udpout://127.0.0.1:17000"), ConnectionResult::Success);
1 ✔
300

301
    // Ground station discovers the autopilot system
302
    auto maybe_system = mavsdk_groundstation.first_autopilot(10.0);
1 ✔
303
    ASSERT_TRUE(maybe_system);
1 ✔
304
    auto system = maybe_system.value();
1 ✔
305
    ASSERT_TRUE(system->has_autopilot());
1 ✔
306

307
    // Wait for autopilot instance to discover the connection to the ground station
308
    LogInfo() << "Waiting for autopilot system to connect...";
1 ✔
309
    while (mavsdk_autopilot.systems().size() == 0) {
2 ✔
310
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
1 ✔
311
    }
312

313
    // Create telemetry instances
314
    auto telemetry = Telemetry{system};
1 ✔
315
    auto telemetry_server = TelemetryServer{mavsdk_autopilot.server_component()};
1 ✔
316

317
    // Set up outgoing JSON message interception on autopilot
318
    auto json_prom = std::promise<Mavsdk::MavlinkMessage>();
1 ✔
319
    auto json_fut = json_prom.get_future();
1 ✔
320
    std::atomic<bool> intercept_called{false};
1 ✔
321

322
    auto json_handle = mavsdk_autopilot.subscribe_outgoing_messages_json(
1 ✔
323
        [&json_prom, &intercept_called](Mavsdk::MavlinkMessage json_message) {
3 ✔
324
            LogInfo() << "Intercepted outgoing JSON message: " << json_message.message_name
2 ✔
325
                      << " to system " << json_message.target_system_id
1 ✔
326
                      << " with fields: " << json_message.fields_json;
1 ✔
327

328
            if (json_message.message_name == "GPS_RAW_INT") {
1 ✔
329
                intercept_called = true;
1 ✔
330
                json_prom.set_value(json_message);
1 ✔
331
                return true; // Don't drop the message
1 ✔
332
            }
333
            return true; // Let other messages through
×
334
        });
335

336
    // Publish GPS data from autopilot
337
    LogInfo() << "Publishing GPS data from autopilot...";
1 ✔
338
    TelemetryServer::RawGps raw_gps{};
1 ✔
339
    raw_gps.timestamp_us = 1234567890123456789;
1 ✔
340
    raw_gps.latitude_deg = 47.3977421; // Zurich coordinates
1 ✔
341
    raw_gps.longitude_deg = 8.5455938;
1 ✔
342
    raw_gps.absolute_altitude_m = 488.0f;
1 ✔
343
    raw_gps.hdop = 1.0f;
1 ✔
344
    raw_gps.vdop = 1.5f;
1 ✔
345
    raw_gps.velocity_m_s = 15.0f;
1 ✔
346
    raw_gps.cog_deg = 90.0f;
1 ✔
347
    raw_gps.altitude_ellipsoid_m = 490.0f;
1 ✔
348
    raw_gps.horizontal_uncertainty_m = 2.0f;
1 ✔
349
    raw_gps.vertical_uncertainty_m = 3.0f;
1 ✔
350
    raw_gps.velocity_uncertainty_m_s = 0.5f;
1 ✔
351
    raw_gps.heading_uncertainty_deg = 5.0f;
1 ✔
352
    raw_gps.yaw_deg = 90.0f;
1 ✔
353

354
    TelemetryServer::GpsInfo gps_info{};
1 ✔
355
    gps_info.num_satellites = 12;
1 ✔
356
    gps_info.fix_type = TelemetryServer::FixType::Fix3D;
1 ✔
357

358
    ASSERT_EQ(
1 ✔
359
        telemetry_server.publish_raw_gps(raw_gps, gps_info), TelemetryServer::Result::Success);
1 ✔
360

361
    // Wait for JSON interception to occur
362
    ASSERT_EQ(json_fut.wait_for(std::chrono::seconds(1)), std::future_status::ready);
1 ✔
363

364
    // Verify intercept was called
365
    ASSERT_TRUE(intercept_called.load());
1 ✔
366

367
    auto intercepted_message = json_fut.get();
1 ✔
368

369
    // Verify the intercepted JSON message structure
370
    EXPECT_EQ(intercepted_message.message_name, "GPS_RAW_INT");
1 ✔
371
    EXPECT_GT(intercepted_message.fields_json.length(), 0u);
1 ✔
372

373
    // Parse JSON to verify field values
374
    Json::Value json;
1 ✔
375
    Json::Reader reader;
1 ✔
376
    ASSERT_TRUE(reader.parse(intercepted_message.fields_json, json));
1 ✔
377

378
    // Verify GPS fields from the intercepted message
379
    EXPECT_EQ(json["time_usec"].asUInt64(), raw_gps.timestamp_us);
1 ✔
380
    EXPECT_EQ(json["fix_type"].asUInt(), static_cast<uint8_t>(gps_info.fix_type));
1 ✔
381
    EXPECT_NEAR(json["lat"].asInt() / 1e7, raw_gps.latitude_deg, 1e-6);
1 ✔
382
    EXPECT_NEAR(json["lon"].asInt() / 1e7, raw_gps.longitude_deg, 1e-6);
1 ✔
383
    EXPECT_NEAR(json["alt"].asInt() / 1e3, raw_gps.absolute_altitude_m, 1.0);
1 ✔
384
    EXPECT_NEAR(json["eph"].asInt() / 1e2, raw_gps.hdop, 0.1);
1 ✔
385
    EXPECT_NEAR(json["epv"].asInt() / 1e2, raw_gps.vdop, 0.1);
1 ✔
386
    EXPECT_NEAR(json["vel"].asInt() / 1e2, raw_gps.velocity_m_s, 0.1);
1 ✔
387
    EXPECT_NEAR(json["cog"].asInt() / 1e2, raw_gps.cog_deg, 1.0);
1 ✔
388
    EXPECT_EQ(json["satellites_visible"].asUInt(), gps_info.num_satellites);
1 ✔
389

390
    LogInfo() << "Successfully tested outgoing JSON message interception";
1 ✔
391
    LogInfo() << "  - Message name: " << intercepted_message.message_name;
1 ✔
392
    LogInfo() << "  - Target system: " << intercepted_message.target_system_id;
1 ✔
393
    LogInfo() << "  - Fields JSON length: " << intercepted_message.fields_json.length();
1 ✔
394

395
    // Cleanup
396
    mavsdk_autopilot.unsubscribe_outgoing_messages_json(json_handle);
1 ✔
397
}
1 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc