/*
 *
 *    Copyright (c) 2021-2022 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.
 */

/**
 * @brief Defines a table of fabrics that have provisioned the device.
 */

#include "LastKnownGoodTime.h"

#include <lib/support/BufferWriter.h>
#include <lib/support/DefaultStorageKeyAllocator.h>
#include <lib/support/SafeInt.h>
#include <platform/ConfigurationManager.h>

namespace chip {

namespace {
// Tags for Last Known Good Time.
constexpr TLV::Tag kLastKnownGoodChipEpochSecondsTag = TLV::ContextTag(0);
} // anonymous namespace

void LastKnownGoodTime::LogTime(const char * msg, System::Clock::Seconds32 chipEpochTime)
{
#if CHIP_PROGRESS_LOGGING
    char buf[26] = { 0 }; // strlen("00000-000-000T000:000:000") == 25
    uint16_t year;
    uint8_t month;
    uint8_t day;
    uint8_t hour;
    uint8_t minute;
    uint8_t second;
    ChipEpochToCalendarTime(chipEpochTime.count(), year, month, day, hour, minute, second);
    snprintf(buf, sizeof(buf), "%04u-%02u-%02uT%02u:%02u:%02u", year, month, day, hour, minute, second);
    ChipLogProgress(TimeService, "%s%s", StringOrNullMarker(msg), buf);
#else
    IgnoreUnusedVariable(msg);
    IgnoreUnusedVariable(chipEpochTime);
#endif
}

CHIP_ERROR LastKnownGoodTime::LoadLastKnownGoodChipEpochTime(System::Clock::Seconds32 & lastKnownGoodChipEpochTime) const
{
    uint8_t buf[LastKnownGoodTimeTLVMaxSize()];
    uint16_t size = sizeof(buf);
    uint32_t seconds;
    ReturnErrorOnFailure(mStorage->SyncGetKeyValue(DefaultStorageKeyAllocator::LastKnownGoodTimeKey().KeyName(), buf, size));
    TLV::ContiguousBufferTLVReader reader;
    reader.Init(buf, size);
    ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
    TLV::TLVType containerType;
    ReturnErrorOnFailure(reader.EnterContainer(containerType));
    ReturnErrorOnFailure(reader.Next(kLastKnownGoodChipEpochSecondsTag));
    ReturnErrorOnFailure(reader.Get(seconds));
    lastKnownGoodChipEpochTime = System::Clock::Seconds32(seconds);
    return CHIP_NO_ERROR;
}

CHIP_ERROR LastKnownGoodTime::StoreLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime) const
{
    uint8_t buf[LastKnownGoodTimeTLVMaxSize()];
    TLV::TLVWriter writer;
    writer.Init(buf);
    TLV::TLVType outerType;
    ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
    ReturnErrorOnFailure(writer.Put(kLastKnownGoodChipEpochSecondsTag, lastKnownGoodChipEpochTime.count()));
    ReturnErrorOnFailure(writer.EndContainer(outerType));
    const auto length = writer.GetLengthWritten();
    VerifyOrReturnError(CanCastTo<uint16_t>(length), CHIP_ERROR_BUFFER_TOO_SMALL);
    ReturnErrorOnFailure(mStorage->SyncSetKeyValue(DefaultStorageKeyAllocator::LastKnownGoodTimeKey().KeyName(), buf,
                                                   static_cast<uint16_t>(length)));
    return CHIP_NO_ERROR;
}

CHIP_ERROR LastKnownGoodTime::Init(PersistentStorageDelegate * storage)
{
    CHIP_ERROR err = CHIP_NO_ERROR;
    mStorage       = storage;
    // 3.5.6.1 Last Known Good UTC Time:
    //
    // "A Node’s initial out-of-box Last Known Good UTC time SHALL be the
    // compile-time of the firmware."
    System::Clock::Seconds32 buildTime;
    SuccessOrExit(err = DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(buildTime));
    System::Clock::Seconds32 storedLastKnownGoodChipEpochTime;
    err = LoadLastKnownGoodChipEpochTime(storedLastKnownGoodChipEpochTime);
    VerifyOrExit(err == CHIP_NO_ERROR || err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND, ;);
    if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
    {
        ChipLogProgress(TimeService, "Last Known Good Time: [unknown]");
    }
    else
    {
        LogTime("Last Known Good Time: ", storedLastKnownGoodChipEpochTime);
    }
    if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND || buildTime > storedLastKnownGoodChipEpochTime)
    {
        // If we have no value in persistence, or the firmware build time is
        // later than the value in persistence, set last known good time to the
        // firmware build time and write back.
        LogTime("Setting Last Known Good Time to firmware build time ", buildTime);
        mLastKnownGoodChipEpochTime.SetValue(buildTime);
        SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(buildTime));
    }
    else
    {
        mLastKnownGoodChipEpochTime.SetValue(storedLastKnownGoodChipEpochTime);
    }
exit:
    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(TimeService, "Failed to init Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format());
    }
    return err;
}

CHIP_ERROR LastKnownGoodTime::SetLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime,
                                                            System::Clock::Seconds32 notBefore)
{
    CHIP_ERROR err = CHIP_NO_ERROR;
    VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
    LogTime("Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value());
    LogTime("New proposed Last Known Good Time: ", lastKnownGoodChipEpochTime);

    // Verify that the passed value is not earlier than the firmware build time.
    System::Clock::Seconds32 buildTime;
    SuccessOrExit(err = DeviceLayer::ConfigurationMgr().GetFirmwareBuildChipEpochTime(buildTime));
    VerifyOrExit(lastKnownGoodChipEpochTime >= buildTime, err = CHIP_ERROR_INVALID_ARGUMENT);
    // Verify that the passed value is not earlier than the passed NotBefore time.
    VerifyOrExit(lastKnownGoodChipEpochTime >= notBefore, err = CHIP_ERROR_INVALID_ARGUMENT);

    // Passed value is valid.  Capture it and write back to persistence.
    //
    // Note that we are purposefully overwriting any previous last known
    // good time that may have been stored as part of a fail-safe context.
    // This is intentional: we don't promise not to change last known good
    // time during the fail-safe timer.  For instance, if the platform has a
    // new, better time source, it is legal to capture it.  If we do, we should
    // both overwrite last known good time and discard the previous value stored
    // for fail safe recovery, as together these comprise a transaction.  By
    // overwriting both, we are fully superseding that transaction with our
    // own, which does not to have a revert feature.
    SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(lastKnownGoodChipEpochTime));
    mLastKnownGoodChipEpochTime.SetValue(lastKnownGoodChipEpochTime);
exit:
    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(TimeService, "Failed to update Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format());
    }
    else
    {
        LogTime("Updating Last Known Good Time to ", lastKnownGoodChipEpochTime);
    }
    return err;
}

CHIP_ERROR LastKnownGoodTime::UpdatePendingLastKnownGoodChipEpochTime(System::Clock::Seconds32 lastKnownGoodChipEpochTime)
{
    CHIP_ERROR err = CHIP_NO_ERROR;
    VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
    LogTime("Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value());
    LogTime("New proposed Last Known Good Time: ", lastKnownGoodChipEpochTime);
    if (lastKnownGoodChipEpochTime > mLastKnownGoodChipEpochTime.Value())
    {
        LogTime("Updating pending Last Known Good Time to ", lastKnownGoodChipEpochTime);
        mLastKnownGoodChipEpochTime.SetValue(lastKnownGoodChipEpochTime);
    }
    else
    {
        ChipLogProgress(TimeService, "Retaining current Last Known Good Time");
    }
exit:
    return err;
}

CHIP_ERROR LastKnownGoodTime::CommitPendingLastKnownGoodChipEpochTime()
{
    CHIP_ERROR err = CHIP_NO_ERROR;
    VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
    LogTime("Committing Last Known Good Time to storage: ", mLastKnownGoodChipEpochTime.Value());
    SuccessOrExit(err = StoreLastKnownGoodChipEpochTime(mLastKnownGoodChipEpochTime.Value()));
exit:
    if (err != CHIP_NO_ERROR)
    {
        ChipLogError(TimeService, "Failed to commit Last Known Good Time: %" CHIP_ERROR_FORMAT, err.Format());
    }
    return err;
}

CHIP_ERROR LastKnownGoodTime::RevertPendingLastKnownGoodChipEpochTime()
{
    CHIP_ERROR err = CHIP_NO_ERROR;
    System::Clock::Seconds32 storedLastKnownGoodChipEpochTime;
    VerifyOrExit(mLastKnownGoodChipEpochTime.HasValue(), err = CHIP_ERROR_INCORRECT_STATE);
    LogTime("Pending Last Known Good Time: ", mLastKnownGoodChipEpochTime.Value());
    SuccessOrExit(err = LoadLastKnownGoodChipEpochTime(storedLastKnownGoodChipEpochTime));
    LogTime("Previous Last Known Good Time: ", storedLastKnownGoodChipEpochTime);
    mLastKnownGoodChipEpochTime.SetValue(storedLastKnownGoodChipEpochTime);
exit:
    if (err != CHIP_NO_ERROR)
    {
        // We do not expect to arrive here unless the kvstore broke or some
        // other code removed our value from persistence.  However, clearing the
        // in-memory value is the right thing to do for all cases.  This will
        // prevent other code from consuming an invalid time.
        ChipLogError(TimeService,
                     "Clearing Last Known Good Time; failed to load a previous value from persistence: %" CHIP_ERROR_FORMAT,
                     err.Format());
        mLastKnownGoodChipEpochTime.ClearValue();
    }
    else
    {
        ChipLogProgress(TimeService, "Reverted Last Known Good Time to previous value");
    }
    return err;
}

} // namespace chip
