text: /*
   *
   *    Copyright (c) 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.
   */
  
  /**
   *    @file
   *          Provides the implementation of the FailSafeContext object.
   */
  
  #include <platform/FailSafeContext.h>
  
  #include <lib/support/DefaultStorageKeyAllocator.h>
  #include <lib/support/SafeInt.h>
  #include <platform/ConfigurationManager.h>
  
  namespace chip {
  namespace DeviceLayer {
  
  namespace {
  constexpr TLV::Tag kFabricIndexTag      = TLV::ContextTag(0);
  constexpr TLV::Tag kAddNocCommandTag    = TLV::ContextTag(1);
  constexpr TLV::Tag kUpdateNocCommandTag = TLV::ContextTag(2);
  } // anonymous namespace
  
  void FailSafeContext::HandleArmFailSafeTimer(System::Layer * layer, void * aAppState)
  {
      FailSafeContext * context = reinterpret_cast<FailSafeContext *>(aAppState);
      context->FailSafeTimerExpired();
  }
  
  void FailSafeContext::HandleDisarmFailSafe(intptr_t arg)
  {
      FailSafeContext * this_ = reinterpret_cast<FailSafeContext *>(arg);
  
      this_->mFailSafeBusy = false;
  
      if (ConfigurationMgr().SetFailSafeArmed(false) != CHIP_NO_ERROR)
      {
          ChipLogError(DeviceLayer, "Failed to set FailSafeArmed config to false");
      }
  
      if (DeleteFromStorage() != CHIP_NO_ERROR)
      {
          ChipLogError(DeviceLayer, "Failed to delete FailSafeContext from configuration");
      }
  }
  
  void FailSafeContext::FailSafeTimerExpired()
  {
      ScheduleFailSafeCleanup(mFabricIndex, mAddNocCommandHasBeenInvoked, mUpdateNocCommandHasBeenInvoked);
  }
  
  void FailSafeContext::ScheduleFailSafeCleanup(FabricIndex fabricIndex, bool addNocCommandInvoked, bool updateNocCommandInvoked)
  {
      mFailSafeArmed                  = false;
      mAddNocCommandHasBeenInvoked    = false;
      mUpdateNocCommandHasBeenInvoked = false;
      mFailSafeBusy                   = true;
  
      ChipDeviceEvent event;
      event.Type                                                = DeviceEventType::kFailSafeTimerExpired;
      event.FailSafeTimerExpired.PeerFabricIndex                = fabricIndex;
      event.FailSafeTimerExpired.AddNocCommandHasBeenInvoked    = addNocCommandInvoked;
      event.FailSafeTimerExpired.UpdateNocCommandHasBeenInvoked = updateNocCommandInvoked;
      CHIP_ERROR status                                         = PlatformMgr().PostEvent(&event);
  
      if (status != CHIP_NO_ERROR)
      {
          ChipLogError(DeviceLayer, "Failed to post fail-safe timer expired: %" CHIP_ERROR_FORMAT, status.Format());
      }
  
      PlatformMgr().ScheduleWork(HandleDisarmFailSafe, reinterpret_cast<intptr_t>(this));
  }
  
  CHIP_ERROR FailSafeContext::ArmFailSafe(FabricIndex accessingFabricIndex, System::Clock::Timeout expiryLength)
  {
      mFailSafeArmed = true;
      mFabricIndex   = accessingFabricIndex;
  
      ReturnErrorOnFailure(DeviceLayer::SystemLayer().StartTimer(expiryLength, HandleArmFailSafeTimer, this));
      ReturnErrorOnFailure(CommitToStorage());
      ReturnErrorOnFailure(ConfigurationMgr().SetFailSafeArmed(true));
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR FailSafeContext::DisarmFailSafe()
  {
      mFailSafeArmed                  = false;
      mAddNocCommandHasBeenInvoked    = false;
      mUpdateNocCommandHasBeenInvoked = false;
  
      DeviceLayer::SystemLayer().CancelTimer(HandleArmFailSafeTimer, this);
  
      ReturnErrorOnFailure(ConfigurationMgr().SetFailSafeArmed(false));
      ReturnErrorOnFailure(DeleteFromStorage());
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR FailSafeContext::SetAddNocCommandInvoked(FabricIndex nocFabricIndex)
  {
      mAddNocCommandHasBeenInvoked = true;
      mFabricIndex                 = nocFabricIndex;
  
      ReturnErrorOnFailure(CommitToStorage());
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR FailSafeContext::SetUpdateNocCommandInvoked()
  {
      mUpdateNocCommandHasBeenInvoked = true;
  
      ReturnErrorOnFailure(CommitToStorage());
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR FailSafeContext::CommitToStorage()
  {
      DefaultStorageKeyAllocator keyAlloc;
      uint8_t buf[FailSafeContextTLVMaxSize()];
      TLV::TLVWriter writer;
      writer.Init(buf);
  
      TLV::TLVType outerType;
      ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
      ReturnErrorOnFailure(writer.Put(kFabricIndexTag, mFabricIndex));
      ReturnErrorOnFailure(writer.Put(kAddNocCommandTag, mAddNocCommandHasBeenInvoked));
      ReturnErrorOnFailure(writer.Put(kUpdateNocCommandTag, mUpdateNocCommandHasBeenInvoked));
      ReturnErrorOnFailure(writer.EndContainer(outerType));
  
      const auto failSafeContextTLVLength = writer.GetLengthWritten();
      VerifyOrReturnError(CanCastTo<uint16_t>(failSafeContextTLVLength), CHIP_ERROR_BUFFER_TOO_SMALL);
  
      return PersistedStorage::KeyValueStoreMgr().Put(keyAlloc.FailSafeContextKey(), buf,
                                                      static_cast<uint16_t>(failSafeContextTLVLength));
  }
  
  CHIP_ERROR FailSafeContext::LoadFromStorage(FabricIndex & fabricIndex, bool & addNocCommandInvoked, bool & updateNocCommandInvoked)
  {
      DefaultStorageKeyAllocator keyAlloc;
      uint8_t buf[FailSafeContextTLVMaxSize()];
      ReturnErrorOnFailure(PersistedStorage::KeyValueStoreMgr().Get(keyAlloc.FailSafeContextKey(), buf, sizeof(buf)));
  
      TLV::ContiguousBufferTLVReader reader;
      reader.Init(buf, sizeof(buf));
      ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
  
      TLV::TLVType containerType;
      ReturnErrorOnFailure(reader.EnterContainer(containerType));
  
      ReturnErrorOnFailure(reader.Next(kFabricIndexTag));
      ReturnErrorOnFailure(reader.Get(fabricIndex));
  
      ReturnErrorOnFailure(reader.Next(kAddNocCommandTag));
      ReturnErrorOnFailure(reader.Get(addNocCommandInvoked));
  
      ReturnErrorOnFailure(reader.Next(kUpdateNocCommandTag));
      ReturnErrorOnFailure(reader.Get(updateNocCommandInvoked));
  
      ReturnErrorOnFailure(reader.VerifyEndOfContainer());
      ReturnErrorOnFailure(reader.ExitContainer(containerType));
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR FailSafeContext::DeleteFromStorage()
  {
      DefaultStorageKeyAllocator keyAlloc;
  
      return PersistedStorage::KeyValueStoreMgr().Delete(keyAlloc.FailSafeContextKey());
  }
  
  } // namespace DeviceLayer
  } // namespace chip
  