Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/MAVLink/Signing/SigningChannel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ bool SigningChannel::init(mavlink_channel_t channel, QByteArrayView key, mavlink
return true;
}

bool SigningChannel::refreshOutgoingTimestamp()
{
QWriteLocker locker(&_lock);
if (!_enabled) {
return false;
}
const uint64_t now = MAVLinkSigning::currentSigningTimestampTicks();
if (now <= _signing.timestamp) {
return false;
}
_signing.timestamp = now;
return true;
}

SigningChannel::TimestampSnapshot SigningChannel::currentTimestampAndName() const
{
QReadLocker locker(&_lock);
Expand Down
6 changes: 6 additions & 0 deletions src/MAVLink/Signing/SigningChannel.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class SigningChannel
/// Returns current timestamp and active key name. Returns {0, ""} when signing is not enabled.
TimestampSnapshot currentTimestampAndName() const;

/// Bump `_signing.timestamp` up to current wall clock; libmavlink only increments per-packet, so an
/// idle outbound path otherwise drifts behind wall clock and triggers OLD_TIMESTAMP rejections on
/// peers that pin signing.timestamp to wall clock (ArduPilot). No-op when not enabled or already ahead.
/// Returns true if the timestamp was advanced.
bool refreshOutgoingTimestamp();

/// While suspended, tryDetectKey is suppressed to block stale-key installs during pending enable.
bool isAutoDetectSuspended() const;

Expand Down
4 changes: 4 additions & 0 deletions src/MAVLink/Signing/SigningController.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ SigningController::SigningController(mavlink_channel_t channel, QObject* parent)
_timeout.setSingleShot(true);
_timeout.setInterval(kTimeout);
connect(&_timeout, &QTimer::timeout, this, &SigningController::_onTimeout);
_wallClockRefresh.setInterval(kWallClockRefreshInterval);
connect(&_wallClockRefresh, &QTimer::timeout, this, [this]() { _channel.refreshOutgoingTimestamp(); });
_wallClockRefresh.start();
qCDebug(SigningControllerLog) << "SigningController ctor — channel" << _mavlinkChannel;
}

SigningController::~SigningController()
{
qCDebug(SigningControllerLog) << "SigningController dtor — channel" << _mavlinkChannel;
_timeout.stop();
_wallClockRefresh.stop();
{
QMutexLocker<QRecursiveMutex> locker(&_fsmMutex);
_autoDetectGuard.reset();
Expand Down
6 changes: 6 additions & 0 deletions src/MAVLink/Signing/SigningController.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,15 @@ class SigningController : public QObject
PendingOp _op;
std::optional<QGC::AutoSuspendGuard> _autoDetectGuard;
QTimer _timeout;
/// 1Hz catch-up of `_signing.timestamp` to wall clock; libmavlink only bumps per-packet so idle
/// outbound paths otherwise sign with a stale value and get rejected by peers that pin signing
/// timestamps to wall clock (mavlink/qgroundcontrol#14375).
QTimer _wallClockRefresh;

static constexpr uint8_t kBadSignatureAlertThreshold = 3;
QGC::EdgeTriggeredCounter<uint8_t> _badSigBurst{kBadSignatureAlertThreshold};

static constexpr auto kTimeout = std::chrono::seconds(5);
/// Refresh interval well under MAVLINK_SIGNING_TIMESTAMP_LIMIT (6s default) on the receiver side.
static constexpr auto kWallClockRefreshInterval = std::chrono::seconds(1);
};
27 changes: 27 additions & 0 deletions test/MAVLink/Signing/SigningTest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -691,4 +691,31 @@ void SigningTest::_testTryDetectKeyInstallsSecureCallback()
signingKeys->removeAllKeys();
}

void SigningTest::_testRefreshOutgoingTimestamp()
{
SigningChannel ch;
const QByteArray rawKey(32, '\x77');
QVERIFY(ch.init(MAVLINK_COMM_0, rawKey, MAVLinkSigning::insecureConnectionAcceptUnsignedCallback));

const mavlink_signing_t* const signing = mavlink_get_channel_status(MAVLINK_COMM_0)->signing;
QVERIFY(signing);

// Simulate the issue #14375 stall: init was 3 minutes ago, no outbound packets sent since.
constexpr uint64_t kThreeMinutesTicks = 3ULL * 60 * 100'000; // 10µs ticks
const uint64_t stale = MAVLinkSigning::currentSigningTimestampTicks() - kThreeMinutesTicks;
const_cast<mavlink_signing_t*>(signing)->timestamp = stale;

QVERIFY(ch.refreshOutgoingTimestamp());
QVERIFY(signing->timestamp >= MAVLinkSigning::currentSigningTimestampTicks() - 100'000); // within 1s of wall clock

// Second call with no wall-clock advance should be a no-op (already at/ahead of wall clock).
const uint64_t afterFirst = signing->timestamp;
const_cast<mavlink_signing_t*>(signing)->timestamp = afterFirst + (10ULL * 100'000); // 10s into the future
QVERIFY(!ch.refreshOutgoingTimestamp());
QCOMPARE(signing->timestamp, afterFirst + (10ULL * 100'000));

QVERIFY(ch.init(MAVLINK_COMM_0, QByteArrayView(), nullptr));
QVERIFY(!ch.refreshOutgoingTimestamp()); // disabled → no-op
}

UT_REGISTER_TEST(SigningTest, TestLabel::Unit)
1 change: 1 addition & 0 deletions test/MAVLink/Signing/SigningTest.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ private slots:
void _testInitSigningWithPersistedTimestamp();
void _testStripSignatureForRetransmitProducesValidCrc();
void _testTryDetectKeyInstallsSecureCallback();
void _testRefreshOutgoingTimestamp();
};
Loading