blob: 21dbefa2493c2e78067881125f294afc9221a6a9 [file] [log] [blame]
/*
*
* Copyright (c) 2020 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
* Provides an implementation of the Configuration key-value store object
* using IniPP on Linux platform.
*
*/
#include <fstream>
#include <string>
#include <unistd.h>
#include <lib/support/Base64.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/IniEscaping.h>
#include <lib/support/logging/CHIPLogging.h>
#include <platform/Linux/CHIPLinuxStorageIni.h>
#include <platform/internal/CHIPDeviceLayerInternal.h>
using namespace chip::IniEscaping;
namespace chip {
namespace DeviceLayer {
namespace Internal {
CHIP_ERROR ChipLinuxStorageIni::Init()
{
return RemoveAll();
}
CHIP_ERROR ChipLinuxStorageIni::GetDefaultSection(std::map<std::string, std::string> & section)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
auto it = mConfigStore.sections.find("DEFAULT");
if (it != mConfigStore.sections.end())
{
section = mConfigStore.sections["DEFAULT"];
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::AddConfig(const std::string & configFile)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::ifstream ifs;
ifs.open(configFile, std::ifstream::in);
if (ifs.is_open())
{
mConfigStore.parse(ifs);
ifs.close();
}
else
{
ChipLogError(DeviceLayer, "Failed to open config file: %s", configFile.c_str());
retval = CHIP_ERROR_OPEN_FAILED;
}
return retval;
}
// Updating a file atomically and durably on Linux requires:
// 1. Writing to a temporary file
// 2. Sync'ing the temp file to commit updated data
// 3. Using rename() to overwrite the existing file
CHIP_ERROR ChipLinuxStorageIni::CommitConfig(const std::string & configFile)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::string tmpPath = configFile + "-XXXXXX";
int fd = mkstemp(&tmpPath[0]);
if (fd != -1)
{
std::ofstream ofs;
ChipLogProgress(DeviceLayer, "writing settings to file (%s)", tmpPath.c_str());
ofs.open(tmpPath, std::ofstream::out | std::ofstream::trunc);
mConfigStore.generate(ofs);
close(fd);
if (rename(tmpPath.c_str(), configFile.c_str()) == 0)
{
ChipLogError(DeviceLayer, "renamed tmp file to file (%s)", configFile.c_str());
}
else
{
ChipLogError(DeviceLayer, "failed to rename (%s), %s (%d)", tmpPath.c_str(), strerror(errno), errno);
retval = CHIP_ERROR_WRITE_FAILED;
}
}
else
{
ChipLogError(DeviceLayer, "failed to open file (%s) for writing", tmpPath.c_str());
retval = CHIP_ERROR_OPEN_FAILED;
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::GetUInt16Value(const char * key, uint16_t & val)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::map<std::string, std::string> section;
retval = GetDefaultSection(section);
if (retval == CHIP_NO_ERROR)
{
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it != section.end())
{
if (!inipp::extract(section[escapedKey], val))
{
retval = CHIP_ERROR_INVALID_ARGUMENT;
}
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::GetUIntValue(const char * key, uint32_t & val)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::map<std::string, std::string> section;
retval = GetDefaultSection(section);
if (retval == CHIP_NO_ERROR)
{
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it != section.end())
{
if (!inipp::extract(section[escapedKey], val))
{
retval = CHIP_ERROR_INVALID_ARGUMENT;
}
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::GetUInt64Value(const char * key, uint64_t & val)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::map<std::string, std::string> section;
retval = GetDefaultSection(section);
if (retval == CHIP_NO_ERROR)
{
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it != section.end())
{
if (!inipp::extract(section[escapedKey], val))
{
retval = CHIP_ERROR_INVALID_ARGUMENT;
}
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::GetStringValue(const char * key, char * buf, size_t bufSize, size_t & outLen)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::map<std::string, std::string> section;
retval = GetDefaultSection(section);
if (retval == CHIP_NO_ERROR)
{
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it != section.end())
{
std::string value;
if (inipp::extract(section[escapedKey], value))
{
size_t len = value.size();
if (len > bufSize - 1)
{
outLen = len;
retval = CHIP_ERROR_BUFFER_TOO_SMALL;
}
else
{
outLen = value.copy(buf, len);
buf[outLen] = '\0';
}
}
else
{
retval = CHIP_ERROR_INVALID_ARGUMENT;
}
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobDataAndLengths(const char * key,
chip::Platform::ScopedMemoryBuffer<char> & encodedData,
size_t & encodedDataLen, size_t & decodedDataLen)
{
size_t encodedDataPaddingLen = 0;
std::map<std::string, std::string> section;
CHIP_ERROR err = GetDefaultSection(section);
if (err != CHIP_NO_ERROR)
{
return err;
}
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it == section.end())
{
return CHIP_ERROR_KEY_NOT_FOUND;
}
std::string value;
// Compute the expectedDecodedLen
if (!inipp::extract(section[escapedKey], value))
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
size_t len = value.size();
if (!encodedData.Alloc(len + 1))
{
return CHIP_ERROR_NO_MEMORY;
}
encodedDataLen = value.copy(encodedData.Get(), len);
encodedData[encodedDataLen] = '\0';
// Check if encoded data was padded. Only "=" or "==" padding combinations are allowed.
if ((encodedDataLen > 0) && (encodedData[encodedDataLen - 1] == '='))
{
encodedDataPaddingLen++;
if ((encodedDataLen > 1) && (encodedData[encodedDataLen - 2] == '='))
encodedDataPaddingLen++;
}
decodedDataLen = ((encodedDataLen - encodedDataPaddingLen) * 3) / 4;
return CHIP_NO_ERROR;
}
CHIP_ERROR ChipLinuxStorageIni::GetBinaryBlobValue(const char * key, uint8_t * decodedData, size_t bufSize, size_t & decodedDataLen)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
chip::Platform::ScopedMemoryBuffer<char> encodedData;
size_t encodedDataLen;
size_t expectedDecodedLen = 0;
retval = GetBinaryBlobDataAndLengths(key, encodedData, encodedDataLen, expectedDecodedLen);
// Check the size
if (retval != CHIP_NO_ERROR)
{
return retval;
}
if (expectedDecodedLen > bufSize)
{
decodedDataLen = expectedDecodedLen;
return CHIP_ERROR_BUFFER_TOO_SMALL;
}
if (encodedDataLen > UINT16_MAX)
{
// We can't even pass this length into Base64Decode.
return CHIP_ERROR_DECODE_FAILED;
}
// Decode it
// Cast is safe because we checked encodedDataLen above.
decodedDataLen = Base64Decode(encodedData.Get(), static_cast<uint16_t>(encodedDataLen), decodedData);
if (decodedDataLen == UINT16_MAX || decodedDataLen > expectedDecodedLen)
{
return CHIP_ERROR_DECODE_FAILED;
}
return CHIP_NO_ERROR;
}
bool ChipLinuxStorageIni::HasValue(const char * key)
{
std::map<std::string, std::string> section;
if (GetDefaultSection(section) != CHIP_NO_ERROR)
return false;
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
return it != section.end();
}
CHIP_ERROR ChipLinuxStorageIni::AddEntry(const char * key, const char * value)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
if ((key != nullptr) && (value != nullptr))
{
std::string escapedKey = EscapeKey(key);
std::map<std::string, std::string> & section = mConfigStore.sections["DEFAULT"];
section[escapedKey] = std::string(value);
}
else
{
ChipLogError(DeviceLayer, "Invalid input argument, failed to add entry");
retval = CHIP_ERROR_INVALID_ARGUMENT;
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::RemoveEntry(const char * key)
{
CHIP_ERROR retval = CHIP_NO_ERROR;
std::map<std::string, std::string> & section = mConfigStore.sections["DEFAULT"];
std::string escapedKey = EscapeKey(key);
auto it = section.find(escapedKey);
if (it != section.end())
{
section.erase(it);
}
else
{
retval = CHIP_ERROR_KEY_NOT_FOUND;
}
return retval;
}
CHIP_ERROR ChipLinuxStorageIni::RemoveAll()
{
mConfigStore.clear();
return CHIP_NO_ERROR;
}
} // namespace Internal
} // namespace DeviceLayer
} // namespace chip