LCOV - code coverage report
Current view: top level - GCS_MAVLink - MAVLink_routing.cpp (source / functions) Hit Total Coverage
Test: lcov.info Lines: 130 162 80.2 %
Date: 2026-09-09 11:02:08 Functions: 10 10 100.0 %

          Line data    Source code
       1             : /*
       2             :    This program is free software: you can redistribute it and/or modify
       3             :    it under the terms of the GNU General Public License as published by
       4             :    the Free Software Foundation, either version 3 of the License, or
       5             :    (at your option) any later version.
       6             : 
       7             :    This program is distributed in the hope that it will be useful,
       8             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
       9             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      10             :    GNU General Public License for more details.
      11             : 
      12             :    You should have received a copy of the GNU General Public License
      13             :    along with this program.  If not, see <http://www.gnu.org/licenses/>.
      14             : */
      15             : 
      16             : /// @file       MAVLink_routing.h
      17             : /// @brief      handle routing of MAVLink packets by sysid/componentid
      18             : 
      19             : #include "GCS_config.h"
      20             : 
      21             : #if HAL_GCS_ENABLED
      22             : 
      23             : #include <stdio.h>
      24             : #include <AP_HAL/AP_HAL.h>
      25             : #include <AP_Common/AP_Common.h>
      26             : #include "GCS.h"
      27             : #include "MAVLink_routing.h"
      28             : 
      29             : #include <AP_ADSB/AP_ADSB.h>
      30             : 
      31             : extern const AP_HAL::HAL& hal;
      32             : 
      33             : #define ROUTING_DEBUG 0
      34             : 
      35             : // constructor
      36        2193 : MAVLink_routing::MAVLink_routing(void) : num_routes(0) {}
      37             : 
      38             : /*
      39             :   forward a MAVLink message to the right port. This also
      40             :   automatically learns the route for the sender if it is not
      41             :   already known.
      42             :   
      43             :   This returns true if the message should be processed locally
      44             : 
      45             :   Theory of MAVLink routing:
      46             : 
      47             :   When a flight controller receives a message it should process it
      48             :   locally if any of these conditions hold:
      49             : 
      50             :     1a) the message has no target_system field
      51             : 
      52             :     1b) the message has a target_system of zero
      53             : 
      54             :     1c) the message has the flight controllers target system and has no
      55             :        target_component field
      56             : 
      57             :     1d) the message has the flight controllers target system and has
      58             :        the flight controllers target_component 
      59             : 
      60             :     1e) the message has the flight controllers target system and the
      61             :         flight controller has not seen any messages on any of its links
      62             :         from a system that has the messages
      63             :         target_system/target_component combination
      64             : 
      65             :   When a flight controller receives a message it should forward it
      66             :   onto another different link if any of these conditions hold for that
      67             :   link: 
      68             : 
      69             :     2a) the message has no target_system field
      70             : 
      71             :     2b) the message has a target_system of zero
      72             : 
      73             :     2c) the message does not have the flight controllers target_system
      74             :         and the flight controller has seen a message from the messages
      75             :         target_system on the link
      76             : 
      77             :     2d) the message has the flight controllers target_system and has a
      78             :         target_component field and the flight controllers has seen a
      79             :         message from the target_system/target_component combination on
      80             :         the link
      81             : 
      82             : Note: This proposal assumes that ground stations will not send command
      83             : packets to a non-broadcast destination (sysid/compid combination)
      84             : until they have received at least one package from that destination
      85             : over the link. This is essential to prevent a flight controller from
      86             : acting on a message that is not meant for it. For example, a PARAM_SET
      87             : cannot be sent to a specific sysid/compid combination until the GCS
      88             : has seen a packet from that sysid/compid combination on the link. 
      89             : 
      90             : The GCS must also reset what sysid/compid combinations it has seen on
      91             : a link when it sees a SYSTEM_TIME message with a decrease in
      92             : time_boot_ms from a particular sysid/compid. That is essential to
      93             : detect a reset of the flight controller, which implies a reset of its
      94             : routing table.
      95             : 
      96             : */
      97     2784593 : bool MAVLink_routing::check_and_forward(uint8_t framing_status,
      98             :                                         GCS_MAVLINK &in_link,
      99             :                                         const mavlink_message_t &msg)
     100             : {
     101             :     // handle the case of loopback of our own messages, due to
     102             :     // incorrect serial configuration.
     103     2784593 :     if (msg.sysid == mavlink_system.sysid &&
     104       21901 :         msg.compid == mavlink_system.compid) {
     105         103 :         return false;  // do not process locally
     106             :     }
     107             : 
     108     2784490 :     switch (framing_status) {
     109     2784490 :     case MAVLINK_FRAMING_OK:
     110     2784490 :         break;
     111           0 :     case MAVLINK_FRAMING_BAD_CRC:
     112           0 :         if (in_link.option_enabled(GCS_MAVLINK::Option::FORWARD_BAD_CRC) &&
     113           0 :             msg.msgid != MAVLINK_MSG_ID_RADIO &&
     114           0 :             msg.msgid != MAVLINK_MSG_ID_RADIO_STATUS &&
     115           0 :             msg.msgid != MAVLINK_MSG_ID_ADSB_VEHICLE) {
     116           0 :             forward(in_link, msg);
     117             :         }
     118           0 :         return false;  // do not process locally
     119           0 :     case MAVLINK_FRAMING_BAD_SIGNATURE:
     120             :     default:
     121           0 :         return false;  // do not process locally
     122             :     }
     123             : 
     124             : #if HAL_SOLO_GIMBAL_ENABLED
     125             :     // check if a Gopro is connected. If yes, we allow the routing
     126             :     // of mavlink messages to a private channel (Solo Gimbal case)
     127     2784490 :     if (!gopro_status_check && (msg.msgid == MAVLINK_MSG_ID_GOPRO_HEARTBEAT)) {
     128           0 :        gopro_status_check = true;
     129           0 :        GCS_SEND_TEXT(MAV_SEVERITY_NOTICE, "GoPro in Solo gimbal detected");
     130             :     }
     131             : #endif // HAL_SOLO_GIMBAL_ENABLED
     132             : 
     133             :     // learn new routes including private channels
     134             :     // so that find_mav_type works for all channels
     135     2784490 :     learn_route(in_link, msg);
     136             : 
     137     2784490 :     if (msg.msgid == MAVLINK_MSG_ID_RADIO ||
     138     2784490 :         msg.msgid == MAVLINK_MSG_ID_RADIO_STATUS) {
     139             :         // don't forward RADIO packets
     140           0 :         return true;
     141             :     }
     142             : 
     143     2784490 :     const bool from_private_channel = in_link.is_private();
     144             : 
     145     2784490 :     if (msg.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
     146             :         // heartbeat needs special handling
     147     1064198 :         if (!from_private_channel) {
     148     1063976 :             handle_heartbeat(in_link, msg);
     149             :         }
     150     1064198 :         return true;
     151             :     }
     152             : 
     153             :     /*
     154             :      * Note that if you are looking at handling routing especially for
     155             :      * certain packets here then you may need to add your message to
     156             :      * the list of IDs we will not forward *even if* the option to
     157             :      * send bad-CRC packets has been set (this is in a switch
     158             :      * statement, above).
     159             :      */
     160             : 
     161             : #if HAL_ADSB_ENABLED
     162     1720292 :     if (msg.msgid == MAVLINK_MSG_ID_ADSB_VEHICLE) {
     163             :         // if enabled ADSB packets are not forwarded, they have their own stream rate
     164          15 :         const AP_ADSB *adsb = AP::ADSB();
     165          15 :         if ((adsb != nullptr) && (adsb->enabled())) {
     166           9 :             return true;
     167             :         }
     168             :     }
     169             : #endif
     170             : 
     171     1720283 :     return forward(in_link, msg);
     172             : }
     173             : 
     174     1720283 : bool MAVLink_routing::forward(GCS_MAVLINK &in_link,
     175             :                               const mavlink_message_t &msg)
     176             : {
     177             :     // extract the targets for this packet
     178     1720283 :     int16_t target_system = -1;
     179     1720283 :     int16_t target_component = -1;
     180     1720283 :     get_targets(msg, target_system, target_component);
     181             : 
     182     1720283 :     bool broadcast_system = (target_system == 0 || target_system == -1);
     183     1720283 :     bool broadcast_component = (target_component == 0 || target_component == -1);
     184     1720283 :     bool match_system = broadcast_system || (target_system == mavlink_system.sysid);
     185     2368450 :     bool match_component = match_system && (broadcast_component || 
     186      648167 :                                             (target_component == mavlink_system.compid));
     187     1720283 :     bool process_locally = match_system && match_component;
     188             : 
     189             :     // don't ever forward data from a private channel
     190             :     // unless a Gopro camera is connected to a Solo gimbal
     191     1720283 :     const bool from_private_channel = in_link.is_private();
     192     1720283 :     bool should_process_locally = from_private_channel;
     193             : #if HAL_SOLO_GIMBAL_ENABLED
     194     1720283 :     if (gopro_status_check) {
     195           0 :         should_process_locally = false;
     196             :     }
     197             : #endif
     198     1720283 :     if (should_process_locally) {
     199       18221 :         return process_locally;
     200             :     }
     201             : 
     202     1702062 :     if (process_locally && !broadcast_system && !broadcast_component) {
     203             :         // nothing more to do - it can only be for us
     204      625158 :         return true;
     205             :     }
     206             : 
     207             :     // forward on any channels matching the targets
     208     1076904 :     bool forwarded = false;
     209             :     bool sent_to_chan[MAVLINK_COMM_NUM_BUFFERS];
     210     1076904 :     memset(sent_to_chan, 0, sizeof(sent_to_chan));
     211     2324635 :     for (uint8_t i=0; i<num_routes; i++) {
     212             : 
     213             :         // Skip if channel is private and the target system or component IDs do not match
     214     1247731 :         GCS_MAVLINK *out_link = gcs().chan(routes[i].channel);
     215     1247731 :         if (out_link == nullptr) {
     216             :             // this is bad
     217           0 :             continue;
     218             :         }
     219     1249240 :         if (out_link->is_private() &&
     220        1509 :             (target_system != routes[i].sysid ||
     221           0 :              target_component != routes[i].compid)) {
     222        1509 :             continue;
     223             :         }
     224             : 
     225     1246222 :         if (broadcast_system || (target_system == routes[i].sysid &&
     226           0 :                                  (broadcast_component || 
     227           0 :                                   target_component == routes[i].compid ||
     228           0 :                                   !match_system))) {
     229             : 
     230     1236402 :             if (&in_link != out_link && !sent_to_chan[routes[i].channel]) {
     231      158355 :                 if (out_link->check_payload_size(msg.len)) {
     232             : #if ROUTING_DEBUG
     233             :                     ::printf("fwd msg %u from chan %u on chan %u sysid=%d compid=%d\n",
     234             :                              msg.msgid,
     235             :                              (unsigned)in_link.get_chan(),
     236             :                              (unsigned)routes[i].channel,
     237             :                              (int)target_system,
     238             :                              (int)target_component);
     239             : #endif
     240      130909 :                     _mavlink_resend_uart(routes[i].channel, &msg);
     241             :                 }
     242      158355 :                 sent_to_chan[routes[i].channel] = true;
     243      158355 :                 forwarded = true;
     244             :             }
     245             :         }
     246             :     }
     247             : 
     248     1076904 :     if ((!forwarded && match_system) ||
     249             :         broadcast_system) {
     250     1076850 :         process_locally = true;
     251             :     }
     252             : 
     253     1076904 :     return process_locally;
     254             : }
     255             : 
     256             : /*
     257             :   send a MAVLink message to all components with this vehicle's system id
     258             : 
     259             :   This is a no-op if no routes to components have been learned
     260             : */
     261          21 : void MAVLink_routing::send_to_components(uint32_t msgid, const char *pkt, uint8_t pkt_len)
     262             : {
     263          21 :     const mavlink_msg_entry_t *entry = mavlink_get_msg_entry(msgid);
     264          21 :     if (entry == nullptr) {
     265           0 :         return;
     266             :     }
     267          21 :     send_to_components(pkt, entry, pkt_len);
     268             : }
     269             : 
     270          21 : void MAVLink_routing::send_to_components(const char *pkt, const mavlink_msg_entry_t *entry, const uint8_t pkt_len)
     271             : {
     272          21 :     bool sent_to_chan[MAVLINK_COMM_NUM_BUFFERS] {};
     273             : 
     274             :     // check learned routes
     275          53 :     for (uint8_t i=0; i<num_routes; i++) {
     276          32 :         if (routes[i].sysid != mavlink_system.sysid) {
     277             :             // our system ID hasn't been seen on this link
     278          27 :             continue;
     279             :         }
     280           5 :         if (sent_to_chan[routes[i].channel]) {
     281             :             // we've already send it on this link
     282           0 :             continue;
     283             :         }
     284           5 :         if (comm_get_txspace(routes[i].channel) <
     285           5 :             ((uint16_t)entry->max_msg_len) + GCS_MAVLINK::packet_overhead_chan(routes[i].channel)) {
     286             :             // it doesn't fit on this channel
     287           0 :             continue;
     288             :         }
     289             : #if ROUTING_DEBUG
     290             :         ::printf("send msg %u on chan %u sysid=%u compid=%u\n",
     291             :                  entry->msgid,
     292             :                  (unsigned)routes[i].channel,
     293             :                  (unsigned)routes[i].sysid,
     294             :                  (unsigned)routes[i].compid);
     295             : #endif
     296             : #if CONFIG_HAL_BOARD == HAL_BOARD_SITL
     297           5 :         if (entry->max_msg_len > pkt_len) {
     298           0 :             AP_HAL::panic("Passed packet message length (%u > %u)",
     299           0 :                           entry->max_msg_len, pkt_len);
     300             :         }
     301             : #endif
     302          10 :         _mav_finalize_message_chan_send(routes[i].channel,
     303           5 :                                         entry->msgid,
     304             :                                         pkt,
     305           5 :                                         entry->min_msg_len,
     306           5 :                                         MIN(entry->max_msg_len, pkt_len),
     307           5 :                                         entry->crc_extra);
     308           5 :         sent_to_chan[routes[i].channel] = true;
     309             :     }
     310          21 : }
     311             : 
     312             : /*
     313             :   search for the first vehicle or component in the routing table with given mav_type and retrieve it's sysid, compid and channel
     314             :   returns true if a match is found
     315             :  */
     316          10 : bool MAVLink_routing::find_by_mavtype(uint8_t mavtype, uint8_t &sysid, uint8_t &compid, mavlink_channel_t &channel)
     317             : {
     318             :     // check learned routes
     319          20 :     for (uint8_t i=0; i<num_routes; i++) {
     320          10 :         if (routes[i].mavtype == mavtype) {
     321           0 :             sysid = routes[i].sysid;
     322           0 :             compid = routes[i].compid;
     323           0 :             channel = routes[i].channel;
     324           0 :             return true;
     325             :         }
     326             :     }
     327             : 
     328             :     // if we've reached we have not found the component
     329          10 :     return false;
     330             : }
     331             : 
     332             : /*
     333             :   search for the first vehicle or component in the routing table with given mav_type and component id and retrieve its sysid and channel
     334             :   returns true if a match is found
     335             :  */
     336           8 : bool MAVLink_routing::find_by_mavtype_and_compid(uint8_t mavtype, uint8_t compid, uint8_t &sysid, mavlink_channel_t &channel) const
     337             : {
     338          28 :     for (uint8_t i=0; i<num_routes; i++) {
     339          28 :         if ((routes[i].mavtype == mavtype) && (routes[i].compid == compid)) {
     340           8 :             sysid = routes[i].sysid;
     341           8 :             channel = routes[i].channel;
     342           8 :             return true;
     343             :         }
     344             :     }
     345           0 :     return false;
     346             : }
     347             : 
     348             : /*
     349             :   see if the message is for a new route and learn it
     350             : */
     351     2784490 : void MAVLink_routing::learn_route(GCS_MAVLINK &in_link, const mavlink_message_t &msg)
     352             : {
     353             :     uint8_t i;
     354     2784490 :     if (msg.sysid == 0) {
     355             :         // don't learn routes to the broadcast system
     356           0 :         return;
     357             :     }
     358     2784490 :     if (msg.sysid == mavlink_system.sysid &&
     359       21798 :         msg.compid == mavlink_system.compid) {
     360             :         // don't learn routes to ourself.  We know where we are.
     361           0 :         return;
     362             :     }
     363     2784490 :     if (msg.sysid == mavlink_system.sysid &&
     364       21798 :         msg.compid == MAV_COMP_ID_ALL) {
     365             :         // don't learn routes to the broadcast component ID for our
     366             :         // own system id.  We should still broadcast these, but we
     367             :         // should also process them locally.
     368           0 :         return;
     369             :     }
     370     2784490 :     const mavlink_channel_t in_channel = in_link.get_chan();
     371     2865757 :     for (i=0; i<num_routes; i++) {
     372     2863561 :         if (routes[i].sysid == msg.sysid &&
     373     2790397 :             routes[i].compid == msg.compid &&
     374     2782807 :             routes[i].channel == in_channel) {
     375     2782294 :             if (routes[i].mavtype == 0 && msg.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
     376          22 :                 routes[i].mavtype = mavlink_msg_heartbeat_get_type(&msg);
     377             :             }
     378     2782294 :             break;
     379             :         }
     380             :     }
     381     2784490 :     if (i == num_routes && i<MAVLINK_MAX_ROUTES) {
     382        2196 :         routes[i].sysid = msg.sysid;
     383        2196 :         routes[i].compid = msg.compid;
     384        2196 :         routes[i].channel = in_channel;
     385        2196 :         if (msg.msgid == MAVLINK_MSG_ID_HEARTBEAT) {
     386        2167 :             routes[i].mavtype = mavlink_msg_heartbeat_get_type(&msg);
     387             :         }
     388        2196 :         num_routes++;
     389             : #if ROUTING_DEBUG
     390             :         ::printf("learned route %u %u via %u\n",
     391             :                  (unsigned)msg.sysid,
     392             :                  (unsigned)msg.compid,
     393             :                  (unsigned)in_channel);
     394             : #endif
     395             :     }
     396             : }
     397             : 
     398             : 
     399             : /*
     400             :   special handling for heartbeat messages. To ensure routing
     401             :   propagation heartbeat messages need to be forwarded on all channels
     402             :   except channels where the sysid/compid of the heartbeat could come from
     403             : */
     404     1063976 : void MAVLink_routing::handle_heartbeat(GCS_MAVLINK &link, const mavlink_message_t &msg)
     405             : {
     406     1063976 :     uint16_t mask = GCS_MAVLINK::active_channel_mask() & ~GCS_MAVLINK::private_channel_mask();
     407             : 
     408     1063976 :     const mavlink_channel_t in_channel = link.get_chan();
     409             : 
     410             :     // don't send on the incoming channel. This should only matter if
     411             :     // the routing table is full
     412     1063976 :     mask &= ~(1U<<(in_channel-MAVLINK_COMM_0));
     413             :     
     414             :     // mask out channels that do not want the heartbeat to be forwarded
     415     1063976 :     mask &= ~no_route_mask;
     416             :     
     417             :     // mask out channels that are known sources for this sysid/compid
     418     2227235 :     for (uint8_t i=0; i<num_routes; i++) {
     419     1163259 :         if (routes[i].sysid == msg.sysid && routes[i].compid == msg.compid) {
     420     1064760 :             mask &= ~(1U<<((unsigned)(routes[i].channel-MAVLINK_COMM_0)));
     421             :         }
     422             :     }
     423             : 
     424     1063976 :     if (mask == 0) {
     425             :         // nothing to send to
     426      977289 :         return;
     427             :     }
     428             : 
     429             :     // send on the remaining channels
     430     1473679 :     for (uint8_t i=0; i<MAVLINK_COMM_NUM_BUFFERS; i++) {
     431     1386992 :         if (mask & (1U<<i)) {
     432       88247 :             mavlink_channel_t channel = (mavlink_channel_t)(MAVLINK_COMM_0 + i);
     433       88247 :             if (comm_get_txspace(channel) >= ((uint16_t)msg.len) +
     434       88247 :                 GCS_MAVLINK::packet_overhead_chan(channel)) {
     435             : #if ROUTING_DEBUG
     436             :                 ::printf("fwd HB from chan %u on chan %u from sysid=%u compid=%u\n",
     437             :                          (unsigned)in_channel,
     438             :                          (unsigned)channel,
     439             :                          (unsigned)msg.sysid,
     440             :                          (unsigned)msg.compid);
     441             : #endif
     442       56402 :                 _mavlink_resend_uart(channel, &msg);
     443             :             }
     444             :         }
     445             :     }
     446             : }
     447             : 
     448             : 
     449             : /*
     450             :   extract target sysid and compid from a message. int16_t is used so
     451             :   that the caller can set them to -1 and know when a sysid or compid
     452             :   target is found in the message
     453             : */
     454     1720283 : void MAVLink_routing::get_targets(const mavlink_message_t &msg, int16_t &sysid, int16_t &compid)
     455             : {
     456     1720283 :     const mavlink_msg_entry_t *msg_entry = mavlink_get_msg_entry(msg.msgid);
     457     1720283 :     if (msg_entry == nullptr) {
     458           0 :         return;
     459             :     }
     460     1720283 :     if (msg_entry->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_SYSTEM) {
     461      648334 :         sysid = _MAV_RETURN_uint8_t(&msg,  msg_entry->target_system_ofs);
     462             :     }
     463     1720283 :     if (msg_entry->flags & MAV_MSG_ENTRY_FLAG_HAVE_TARGET_COMPONENT) {
     464      648257 :         compid = _MAV_RETURN_uint8_t(&msg,  msg_entry->target_component_ofs);
     465             :     }
     466             : }
     467             : 
     468             : #endif  // HAL_GCS_ENABLED

Generated by: LCOV version 1.14