blob: 790b4498b96f3c9d5be0f2c76042f41d53eeb368 [file] [log] [blame]
/*
* Copyright (c) 2021 Project CHIP Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @file
* This file defines the CHIP message counters of remote nodes.
*
*/
#pragma once
#include <array>
#include <bitset>
#include <lib/support/Span.h>
namespace chip {
namespace Transport {
class PeerMessageCounter
{
public:
static constexpr size_t kChallengeSize = 8;
PeerMessageCounter() : mStatus(Status::NotSynced) {}
~PeerMessageCounter() { Reset(); }
void Reset()
{
switch (mStatus)
{
case Status::NotSynced:
break;
case Status::SyncInProcess:
mSyncInProcess.~SyncInProcess();
break;
case Status::Synced:
mSynced.~Synced();
break;
}
mStatus = Status::NotSynced;
}
bool IsSynchronizing() { return mStatus == Status::SyncInProcess; }
bool IsSynchronized() { return mStatus == Status::Synced; }
void SyncStarting(FixedByteSpan<kChallengeSize> challenge)
{
VerifyOrDie(mStatus == Status::NotSynced);
mStatus = Status::SyncInProcess;
new (&mSyncInProcess) SyncInProcess();
::memcpy(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize);
}
void SyncFailed() { Reset(); }
CHIP_ERROR VerifyChallenge(uint32_t counter, FixedByteSpan<kChallengeSize> challenge)
{
if (mStatus != Status::SyncInProcess)
{
return CHIP_ERROR_INCORRECT_STATE;
}
if (::memcmp(mSyncInProcess.mChallenge.data(), challenge.data(), kChallengeSize) != 0)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
mSyncInProcess.~SyncInProcess();
mStatus = Status::Synced;
new (&mSynced) Synced();
mSynced.mMaxCounter = counter;
mSynced.mWindow.reset(); // reset all bits, accept all packets in the window
return CHIP_NO_ERROR;
}
CHIP_ERROR Verify(uint32_t counter) const
{
if (mStatus != Status::Synced)
{
return CHIP_ERROR_INCORRECT_STATE;
}
if (counter <= mSynced.mMaxCounter)
{
uint32_t offset = mSynced.mMaxCounter - counter;
if (offset >= CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
{
return CHIP_ERROR_MESSAGE_COUNTER_OUT_OF_WINDOW; // outside valid range
}
if (mSynced.mWindow.test(offset))
{
return CHIP_ERROR_DUPLICATE_MESSAGE_RECEIVED; // duplicated, in window
}
}
return CHIP_NO_ERROR;
}
CHIP_ERROR VerifyOrTrustFirst(uint32_t counter)
{
switch (mStatus)
{
case Status::NotSynced:
// Trust and set the counter when not synced
SetCounter(counter);
return CHIP_NO_ERROR;
case Status::Synced: {
CHIP_ERROR err = Verify(counter);
if (err == CHIP_ERROR_MESSAGE_COUNTER_OUT_OF_WINDOW)
{
// According to chip spec, when global unencrypted message
// counter is out of window, the peer may have reset and is
// using another randomize initial value. Trust the new
// counter here.
SetCounter(counter);
err = CHIP_NO_ERROR;
}
return err;
}
default:
VerifyOrDie(false);
return CHIP_ERROR_INTERNAL;
}
}
/**
* @brief
* With the counter verified and the packet MIC also verified by the secure key, we can trust the packet and adjust
* counter states.
*
* @pre Verify(counter) == CHIP_NO_ERROR
*/
void Commit(uint32_t counter)
{
if (counter <= mSynced.mMaxCounter)
{
uint32_t offset = mSynced.mMaxCounter - counter;
mSynced.mWindow.set(offset);
}
else
{
uint32_t offset = counter - mSynced.mMaxCounter;
// advance max counter by `offset`
mSynced.mMaxCounter = counter;
if (offset < CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE)
{
mSynced.mWindow <<= offset;
}
else
{
mSynced.mWindow.reset();
}
mSynced.mWindow.set(0);
}
}
void SetCounter(uint32_t value)
{
Reset();
mStatus = Status::Synced;
new (&mSynced) Synced();
mSynced.mMaxCounter = value;
mSynced.mWindow.reset();
}
uint32_t GetCounter() { return mSynced.mMaxCounter; }
private:
enum class Status
{
NotSynced, // No state associated
SyncInProcess, // mSyncInProcess will be active
Synced, // mSynced will be active
} mStatus;
struct SyncInProcess
{
std::array<uint8_t, kChallengeSize> mChallenge;
};
struct Synced
{
/*
* Past <-- --> Future
* MaxCounter
* |
* v
* | <-- mWindow -->|
* |[n]| ... |[0]|
*/
uint32_t mMaxCounter; // The most recent counter we have seen
std::bitset<CHIP_CONFIG_MESSAGE_COUNTER_WINDOW_SIZE> mWindow;
};
// We should use std::variant here when migrated to C++17
union
{
SyncInProcess mSyncInProcess;
Synced mSynced;
};
};
} // namespace Transport
} // namespace chip