text: /*
   *    Copyright (c) 2022 Project CHIP Authors
   *    All rights reserved.
   *
   *    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 CASE Session object that provides
   *      APIs for constructing a secure session using a certificate from the device's
   *      operational credentials.
   */
  
  #include <protocols/secure_channel/SimpleSessionResumptionStorage.h>
  
  #include <lib/support/Base64.h>
  #include <lib/support/SafeInt.h>
  
  namespace chip {
  
  constexpr TLV::Tag SimpleSessionResumptionStorage::kFabricIndexTag;
  constexpr TLV::Tag SimpleSessionResumptionStorage::kPeerNodeIdTag;
  constexpr TLV::Tag SimpleSessionResumptionStorage::kResumptionIdTag;
  constexpr TLV::Tag SimpleSessionResumptionStorage::kSharedSecretTag;
  constexpr TLV::Tag SimpleSessionResumptionStorage::kCATTag;
  
  const char * SimpleSessionResumptionStorage::StorageKey(DefaultStorageKeyAllocator & keyAlloc, const ScopedNodeId & node)
  {
      return keyAlloc.FabricSession(node.GetFabricIndex(), node.GetNodeId());
  }
  
  const char * SimpleSessionResumptionStorage::StorageKey(DefaultStorageKeyAllocator & keyAlloc, ConstResumptionIdView resumptionId)
  {
      char resumptionIdBase64[BASE64_ENCODED_LEN(resumptionId.size()) + 1];
      auto len                = Base64Encode(resumptionId.data(), resumptionId.size(), resumptionIdBase64);
      resumptionIdBase64[len] = '\0';
      return keyAlloc.SessionResumption(resumptionIdBase64);
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::SaveIndex(const SessionIndex & index)
  {
      std::array<uint8_t, MaxIndexSize()> buf;
      TLV::TLVWriter writer;
      writer.Init(buf);
  
      TLV::TLVType arrayType;
      ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Array, arrayType));
  
      for (size_t i = 0; i < index.mSize; ++i)
      {
          TLV::TLVType innerType;
          ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, innerType));
          ReturnErrorOnFailure(writer.Put(kFabricIndexTag, index.mNodes[i].GetFabricIndex()));
          ReturnErrorOnFailure(writer.Put(kPeerNodeIdTag, index.mNodes[i].GetNodeId()));
          ReturnErrorOnFailure(writer.EndContainer(innerType));
      }
  
      ReturnErrorOnFailure(writer.EndContainer(arrayType));
  
      const auto len = writer.GetLengthWritten();
      VerifyOrReturnError(CanCastTo<uint16_t>(len), CHIP_ERROR_BUFFER_TOO_SMALL);
  
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncSetKeyValue(keyAlloc.SessionResumptionIndex(), buf.data(), static_cast<uint16_t>(len)));
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::LoadIndex(SessionIndex & index)
  {
      std::array<uint8_t, MaxIndexSize()> buf;
      uint16_t len = static_cast<uint16_t>(buf.size());
  
      DefaultStorageKeyAllocator keyAlloc;
      if (mStorage->SyncGetKeyValue(keyAlloc.SessionResumptionIndex(), buf.data(), len) != CHIP_NO_ERROR)
      {
          index.mSize = 0;
          return CHIP_NO_ERROR;
      }
  
      TLV::ContiguousBufferTLVReader reader;
      reader.Init(buf.data(), len);
  
      ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Array, TLV::AnonymousTag()));
      TLV::TLVType arrayType;
      ReturnErrorOnFailure(reader.EnterContainer(arrayType));
  
      size_t count = 0;
      CHIP_ERROR err;
      while ((err = reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag())) == CHIP_NO_ERROR)
      {
          if (count >= ArraySize(index.mNodes))
          {
              return CHIP_ERROR_NO_MEMORY;
          }
  
          TLV::TLVType containerType;
          ReturnErrorOnFailure(reader.EnterContainer(containerType));
  
          FabricIndex fabricIndex;
          ReturnErrorOnFailure(reader.Next(kFabricIndexTag));
          ReturnErrorOnFailure(reader.Get(fabricIndex));
  
          NodeId peerNodeId;
          ReturnErrorOnFailure(reader.Next(kPeerNodeIdTag));
          ReturnErrorOnFailure(reader.Get(peerNodeId));
  
          index.mNodes[count++] = ScopedNodeId(peerNodeId, fabricIndex);
  
          ReturnErrorOnFailure(reader.ExitContainer(containerType));
      }
  
      if (err != CHIP_END_OF_TLV)
      {
          return err;
      }
  
      ReturnErrorOnFailure(reader.ExitContainer(arrayType));
      ReturnErrorOnFailure(reader.VerifyEndOfContainer());
  
      index.mSize = count;
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::SaveLink(ConstResumptionIdView resumptionId, const ScopedNodeId & node)
  {
      // Save a link from resumptionId to node, in key: /g/s/<resumptionId>
      std::array<uint8_t, MaxScopedNodeIdSize()> buf;
      TLV::TLVWriter writer;
      writer.Init(buf);
  
      TLV::TLVType outerType;
      ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
      ReturnErrorOnFailure(writer.Put(kFabricIndexTag, node.GetFabricIndex()));
      ReturnErrorOnFailure(writer.Put(kPeerNodeIdTag, node.GetNodeId()));
      ReturnErrorOnFailure(writer.EndContainer(outerType));
  
      const auto len = writer.GetLengthWritten();
      VerifyOrDie(CanCastTo<uint16_t>(len));
  
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncSetKeyValue(StorageKey(keyAlloc, resumptionId), buf.data(), static_cast<uint16_t>(len)));
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::LoadLink(ConstResumptionIdView resumptionId, ScopedNodeId & node)
  {
      std::array<uint8_t, MaxScopedNodeIdSize()> buf;
      uint16_t len = static_cast<uint16_t>(buf.size());
  
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncGetKeyValue(StorageKey(keyAlloc, resumptionId), buf.data(), len));
  
      TLV::ContiguousBufferTLVReader reader;
      reader.Init(buf.data(), len);
  
      ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
      TLV::TLVType containerType;
      ReturnErrorOnFailure(reader.EnterContainer(containerType));
  
      FabricIndex fabricIndex;
      ReturnErrorOnFailure(reader.Next(kFabricIndexTag));
      ReturnErrorOnFailure(reader.Get(fabricIndex));
  
      NodeId peerNodeId;
      ReturnErrorOnFailure(reader.Next(kPeerNodeIdTag));
      ReturnErrorOnFailure(reader.Get(peerNodeId));
  
      ReturnErrorOnFailure(reader.ExitContainer(containerType));
      ReturnErrorOnFailure(reader.VerifyEndOfContainer());
  
      node = ScopedNodeId(peerNodeId, fabricIndex);
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::DeleteLink(ConstResumptionIdView resumptionId)
  {
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncDeleteKeyValue(StorageKey(keyAlloc, resumptionId)));
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::SaveState(const ScopedNodeId & node, ConstResumptionIdView resumptionId,
                                                       const Crypto::P256ECDHDerivedSecret & sharedSecret, const CATValues & peerCATs)
  {
      // Save session state into key: /f/<fabricIndex>/s/<nodeId>
      std::array<uint8_t, MaxStateSize()> buf;
      TLV::TLVWriter writer;
      writer.Init(buf);
  
      TLV::TLVType outerType;
      ReturnErrorOnFailure(writer.StartContainer(TLV::AnonymousTag(), TLV::kTLVType_Structure, outerType));
  
      ReturnErrorOnFailure(writer.Put(kResumptionIdTag, resumptionId));
  
      ReturnErrorOnFailure(writer.Put(kSharedSecretTag, ByteSpan(sharedSecret.ConstBytes(), sharedSecret.Length())));
  
      CATValues::Serialized cat;
      peerCATs.Serialize(cat);
      ReturnErrorOnFailure(writer.Put(kCATTag, ByteSpan(cat)));
  
      ReturnErrorOnFailure(writer.EndContainer(outerType));
  
      const auto len = writer.GetLengthWritten();
      VerifyOrDie(CanCastTo<uint16_t>(len));
  
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncSetKeyValue(StorageKey(keyAlloc, node), buf.data(), static_cast<uint16_t>(len)));
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::LoadState(const ScopedNodeId & node, ResumptionIdStorage & resumptionId,
                                                       Crypto::P256ECDHDerivedSecret & sharedSecret, CATValues & peerCATs)
  {
      std::array<uint8_t, MaxStateSize()> buf;
      uint16_t len = static_cast<uint16_t>(buf.size());
  
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncGetKeyValue(StorageKey(keyAlloc, node), buf.data(), len));
  
      TLV::ContiguousBufferTLVReader reader;
      reader.Init(buf.data(), len);
  
      ReturnErrorOnFailure(reader.Next(TLV::kTLVType_Structure, TLV::AnonymousTag()));
      TLV::TLVType containerType;
      ReturnErrorOnFailure(reader.EnterContainer(containerType));
  
      ByteSpan resumptionIdSpan;
      ReturnErrorOnFailure(reader.Next(kResumptionIdTag));
      ReturnErrorOnFailure(reader.Get(resumptionIdSpan));
      VerifyOrReturnError(resumptionIdSpan.size() == resumptionId.size(), CHIP_ERROR_KEY_NOT_FOUND);
      std::copy(resumptionIdSpan.begin(), resumptionIdSpan.end(), resumptionId.begin());
  
      ByteSpan sharedSecretSpan;
      ReturnErrorOnFailure(reader.Next(kSharedSecretTag));
      ReturnErrorOnFailure(reader.Get(sharedSecretSpan));
      VerifyOrReturnError(sharedSecretSpan.size() <= sharedSecret.Capacity(), CHIP_ERROR_BUFFER_TOO_SMALL);
      ::memcpy(sharedSecret.Bytes(), sharedSecretSpan.data(), sharedSecretSpan.size());
      sharedSecret.SetLength(sharedSecretSpan.size());
  
      ByteSpan catSpan;
      ReturnErrorOnFailure(reader.Next(kCATTag));
      ReturnErrorOnFailure(reader.Get(catSpan));
      CATValues::Serialized cat;
      VerifyOrReturnError(sizeof(cat) == catSpan.size(), CHIP_ERROR_INVALID_TLV_ELEMENT);
      ::memcpy(cat, catSpan.data(), catSpan.size());
      peerCATs.Deserialize(cat);
  
      ReturnErrorOnFailure(reader.ExitContainer(containerType));
      ReturnErrorOnFailure(reader.VerifyEndOfContainer());
  
      return CHIP_NO_ERROR;
  }
  
  CHIP_ERROR SimpleSessionResumptionStorage::DeleteState(const ScopedNodeId & node)
  {
      DefaultStorageKeyAllocator keyAlloc;
      ReturnErrorOnFailure(mStorage->SyncDeleteKeyValue(StorageKey(keyAlloc, node)));
      return CHIP_NO_ERROR;
  }
  
  } // namespace chip
  