blob: 60fba68dc4c564db9704e769e7e4a52f7ae152b1 [file] [log] [blame]
/*
*
* Copyright (c) 2025 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.
*/
#include "CommodityTariffAttrsDataMgmt.h"
using namespace chip;
using namespace chip::app;
using namespace chip::Platform;
using namespace chip::app::DataModel;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::Globals;
using namespace chip::app::Clusters::Globals::Structs;
using namespace chip::app::Clusters::CommodityTariff;
using namespace chip::app::Clusters::CommodityTariff::Structs;
using namespace chip::app::CommodityTariffAttrsDataMgmt;
using namespace CommodityTariffConsts;
#define VerifyOrReturnError_LogSend(expr, code, ...) \
do \
{ \
if (!(expr)) \
{ \
ChipLogError(AppServer, __VA_ARGS__); \
VerifyOrReturnError(expr, code); \
} \
} while (false)
namespace CommonUtilities {
static bool HasFeatureInCtx(TariffUpdateCtx * aCtx, Feature aFeature)
{
return aCtx->mFeature.Has(aFeature);
}
/**
* @brief Releases memory allocated for a list of IDs and resets the list
* @param IDs List containing allocated IDs buffer to free. After this call,
* the list will be empty and the buffer pointer nulled.
*
* @note The const_cast is safe because:
* 1. We own the memory allocation (allocated via MemoryAlloc in non-const context)
* 2. MemoryFree doesn't modify the memory, just deallocates it
* 3. The const-ness was only added for interface safety
* 4. This matches the symmetric Alloc/Free pattern we established
*/
static void CleanUpIDs(DataModel::List<const uint32_t> & IDs)
{
if (!IDs.empty() && IDs.data())
{
// Safe const_cast because:
// - We allocated this memory ourselves via non-const allocation
// - MemoryFree won't actually modify the contents
// - The original allocation wasn't truly const (just interface const)
MemoryFree(const_cast<uint32_t *>(IDs.data()));
IDs = DataModel::List<const uint32_t>();
}
}
static bool HasDuplicateIDs(const DataModel::List<const uint32_t> & IDs, std::unordered_set<uint32_t> & seen)
{
for (auto id : IDs)
{
if (!seen.insert(id).second)
{
return true; // Duplicate found
}
}
return false;
}
}; // namespace CommonUtilities
namespace chip {
namespace app {
namespace CommodityTariffAttrsDataMgmt {
using namespace CommodityTariffConsts;
using namespace chip::app::Clusters::CommodityTariff::Structs;
template <typename T>
CHIP_ERROR CTC_BaseDataClass<T>::CopyData(const StructType & input, StructType & output)
{
// Log error since this base implementation doesn't do anything meaningful
ChipLogError(DataManagement, "CopyData() called on base class - this should be overridden!");
// Return appropriate error code
return CHIP_ERROR_NOT_IMPLEMENTED;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<TariffInformationStruct::Type>>::CopyData(const StructType & input,
StructType & output)
{
output.tariffLabel.SetNull();
output.providerName.SetNull();
output.currency.ClearValue();
output.blockMode.SetNull();
if (!input.tariffLabel.IsNull())
{
ReturnErrorOnFailure(
SpanCopier<char>::Copy(input.tariffLabel.Value(), output.tariffLabel, input.tariffLabel.Value().size()));
}
if (!input.providerName.IsNull())
{
ReturnErrorOnFailure(
SpanCopier<char>::Copy(input.providerName.Value(), output.providerName, input.providerName.Value().size()));
}
if (input.currency.HasValue())
{
output.currency.Emplace();
if (input.currency.Value().IsNull())
{
output.currency.Value().SetNull();
}
else
{
output.currency.Value().SetNonNull(input.currency.Value().Value());
}
}
if (!input.blockMode.IsNull())
{
output.blockMode.SetNonNull(input.blockMode.Value());
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayEntryStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.dayEntryID = input.dayEntryID;
output.startTime = input.startTime;
output.duration.ClearValue();
if (input.duration.HasValue())
{
output.duration.SetValue(input.duration.Value());
}
output.randomizationOffset.ClearValue();
if (input.randomizationOffset.HasValue())
{
output.randomizationOffset.SetValue(input.randomizationOffset.Value());
}
output.randomizationType.ClearValue();
if (input.randomizationType.HasValue())
{
output.randomizationType.SetValue(input.randomizationType.Value());
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffComponentStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.tariffComponentID = input.tariffComponentID;
output.price.ClearValue();
if (input.price.HasValue())
{
output.price.Emplace();
output.price.Value().SetNull();
if (!input.price.Value().IsNull())
{
auto & priceInput = input.price.Value().Value();
TariffPriceStruct::Type tmp_price;
tmp_price.priceType = priceInput.priceType;
if (priceInput.price.HasValue())
{
tmp_price.price.SetValue(priceInput.price.Value());
}
if (priceInput.priceLevel.HasValue())
{
tmp_price.priceLevel.SetValue(priceInput.priceLevel.Value());
}
output.price.Value().SetNonNull(tmp_price);
}
}
output.friendlyCredit.ClearValue();
if (input.friendlyCredit.HasValue())
{
output.friendlyCredit.SetValue(input.friendlyCredit.Value());
}
output.auxiliaryLoad.ClearValue();
if (input.auxiliaryLoad.HasValue())
{
output.auxiliaryLoad.Emplace();
output.auxiliaryLoad.Value().number = input.auxiliaryLoad.Value().number;
output.auxiliaryLoad.Value().requiredState = input.auxiliaryLoad.Value().requiredState;
}
output.peakPeriod.ClearValue();
if (input.peakPeriod.HasValue())
{
output.peakPeriod.Emplace();
output.peakPeriod.Value().severity = input.peakPeriod.Value().severity;
output.peakPeriod.Value().peakPeriod = input.peakPeriod.Value().peakPeriod;
}
output.powerThreshold.ClearValue();
if (input.powerThreshold.HasValue())
{
output.powerThreshold.Emplace();
output.powerThreshold.Value() = input.powerThreshold.Value();
}
output.threshold.SetNull();
if (!input.threshold.IsNull())
{
output.threshold.SetNonNull(input.threshold.Value());
}
output.label.ClearValue();
if (input.label.HasValue())
{
output.label.Emplace();
output.label.Value().SetNull();
if (!input.label.Value().IsNull())
{
ReturnErrorOnFailure(
SpanCopier<char>::Copy(input.label.Value().Value(), output.label.Value(), input.label.Value().Value().size()));
}
}
output.predicted.ClearValue();
if (input.predicted.HasValue())
{
output.predicted.SetValue(input.predicted.Value());
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffPeriodStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.label.SetNull();
if (!input.label.IsNull())
{
ReturnErrorOnFailure(SpanCopier<char>::Copy(input.label.Value(), output.label, input.label.Value().size()));
}
ReturnErrorOnFailure(SpanCopier<uint32_t>::Copy(chip::Span<const uint32_t>(input.dayEntryIDs.data(), input.dayEntryIDs.size()),
output.dayEntryIDs, input.dayEntryIDs.size()));
ReturnErrorOnFailure(
SpanCopier<uint32_t>::Copy(chip::Span<const uint32_t>(input.tariffComponentIDs.data(), input.tariffComponentIDs.size()),
output.tariffComponentIDs, input.tariffComponentIDs.size()));
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayPatternStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.dayPatternID = input.dayPatternID;
output.daysOfWeek = input.daysOfWeek;
ReturnErrorOnFailure(SpanCopier<uint32_t>::Copy(chip::Span<const uint32_t>(input.dayEntryIDs.data(), input.dayEntryIDs.size()),
output.dayEntryIDs, input.dayEntryIDs.size()));
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.date = input.date;
output.dayType = input.dayType;
ReturnErrorOnFailure(SpanCopier<uint32_t>::Copy(chip::Span<const uint32_t>(input.dayEntryIDs.data(), input.dayEntryIDs.size()),
output.dayEntryIDs, input.dayEntryIDs.size()));
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<CalendarPeriodStruct::Type>>>::CopyData(const StructType & input,
StructType & output)
{
output.startDate.SetNull();
if (!input.startDate.IsNull())
{
output.startDate.SetNonNull(input.startDate.Value());
}
ReturnErrorOnFailure(
SpanCopier<uint32_t>::Copy(chip::Span<const uint32_t>(input.dayPatternIDs.data(), input.dayPatternIDs.size()),
output.dayPatternIDs, input.dayPatternIDs.size()));
return CHIP_NO_ERROR;
}
template <typename T>
CHIP_ERROR ValidateListEntry(const T & entryNewValue, void * aCtx)
{
// Log error since this base implementation doesn't do anything meaningful
ChipLogError(DataManagement, "CopyData() called on base class - this should be overridden!");
// Return appropriate error code
return CHIP_ERROR_NOT_IMPLEMENTED;
}
CHIP_ERROR ValidateListEntry(const DayPatternStruct::Type & entryNewValue, void * aCtx)
{
auto * ctx = static_cast<TariffUpdateCtx *>(aCtx);
if (entryNewValue.dayEntryIDs.empty() || entryNewValue.dayEntryIDs.size() > kDayPatternItemMaxDayEntryIDs)
return CHIP_ERROR_INVALID_ARGUMENT;
// Check that the current day pattern item has no duplicated dayEntryIDs
if (CommonUtilities::HasDuplicateIDs(entryNewValue.dayEntryIDs, ctx->DayPatternsDayEntryIDs))
{
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ValidateListEntry(const DayEntryStruct::Type & entryNewValue, void * aCtx)
{
auto * ctx = static_cast<TariffUpdateCtx *>(aCtx);
// Check for duplicate IDs
if (!ctx->DayEntryKeyIDs.insert(entryNewValue.dayEntryID).second)
{
ChipLogError(AppServer, "Duplicate dayEntryID found");
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
VerifyOrReturnError_LogSend(entryNewValue.startTime < kDayEntryDurationLimit, CHIP_ERROR_INVALID_ARGUMENT,
"DayEntry startTime must be less than %u", kDayEntryDurationLimit);
if (CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kRandomization))
{
if (entryNewValue.randomizationOffset.HasValue() && entryNewValue.randomizationType.HasValue())
{
VerifyOrReturnError(EnsureKnownEnumValue(entryNewValue.randomizationType.Value()) !=
DayEntryRandomizationTypeEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
}
else
{
ChipLogError(AppServer, "If the RNDM feature is enabled, the randomization* field is required!");
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR ValidateListEntry(const TariffComponentStruct::Type & entryNewValue, void * aCtx)
{
BitMask<Feature> entryFeatures;
auto * ctx = static_cast<TariffUpdateCtx *>(aCtx);
VerifyOrReturnError(entryNewValue.tariffComponentID > 0, CHIP_ERROR_INVALID_ARGUMENT);
if ((ctx->blockMode == BlockModeEnum::kNoBlock) == !entryNewValue.threshold.IsNull())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// entryNewValue.label
if (entryNewValue.label.HasValue() && !entryNewValue.label.Value().IsNull())
{
VerifyOrReturnError(entryNewValue.label.Value().Value().size() <= kTariffComponentMaxLabelLength,
CHIP_ERROR_INVALID_ARGUMENT);
}
// Handle price field validation based on feature support
if (entryNewValue.price.HasValue() && !entryNewValue.price.Value().IsNull())
{
// If price is provided, the Pricing feature MUST be supported
VerifyOrReturnError(CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kPricing), CHIP_ERROR_INVALID_ARGUMENT);
const auto & price = entryNewValue.price.Value().Value();
VerifyOrReturnError(EnsureKnownEnumValue(price.priceType) != TariffPriceTypeEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
entryFeatures.Set(CommodityTariff::Feature::kPricing);
}
// Validate friendlyCredit field
if (entryNewValue.friendlyCredit.HasValue())
{
// If friendlyCredit is provided, the FCRED feature MUST be supported
VerifyOrReturnError(CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kFriendlyCredit),
CHIP_ERROR_INVALID_ARGUMENT);
entryFeatures.Set(CommodityTariff::Feature::kFriendlyCredit);
}
// Validate auxiliaryLoad field
if (entryNewValue.auxiliaryLoad.HasValue())
{
VerifyOrReturnError(CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kAuxiliaryLoad),
CHIP_ERROR_INVALID_ARGUMENT);
const auto & auxiliaryLoad = entryNewValue.auxiliaryLoad.Value();
VerifyOrReturnError(EnsureKnownEnumValue(auxiliaryLoad.requiredState) != AuxiliaryLoadSettingEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
entryFeatures.Set(CommodityTariff::Feature::kAuxiliaryLoad);
}
// Validate peakPeriod field
if (entryNewValue.peakPeriod.HasValue())
{
VerifyOrReturnError(CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kPeakPeriod),
CHIP_ERROR_INVALID_ARGUMENT);
const auto & peakPeriod = entryNewValue.peakPeriod.Value();
VerifyOrReturnError(EnsureKnownEnumValue(peakPeriod.severity) != PeakPeriodSeverityEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(peakPeriod.peakPeriod > 0, CHIP_ERROR_INVALID_ARGUMENT);
entryFeatures.Set(CommodityTariff::Feature::kPeakPeriod);
}
// Validate powerThreshold field
if (entryNewValue.powerThreshold.HasValue())
{
VerifyOrReturnError(CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kPowerThreshold),
CHIP_ERROR_INVALID_ARGUMENT);
const auto & powerThreshold = entryNewValue.powerThreshold.Value();
if (!powerThreshold.powerThresholdSource.IsNull())
{
VerifyOrReturnError(EnsureKnownEnumValue(powerThreshold.powerThresholdSource.Value()) !=
PowerThresholdSourceEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
}
// Additional power threshold validations
if (powerThreshold.powerThreshold.HasValue())
{
VerifyOrReturnError(powerThreshold.powerThreshold.Value() > 0, CHIP_ERROR_INVALID_ARGUMENT);
}
if (powerThreshold.apparentPowerThreshold.HasValue())
{
VerifyOrReturnError(powerThreshold.apparentPowerThreshold.Value() > 0, CHIP_ERROR_INVALID_ARGUMENT);
}
entryFeatures.Set(CommodityTariff::Feature::kPowerThreshold);
}
if (entryNewValue.predicted.HasValue())
{
// No need to check the value itself since it's just a bool
// But we can add debug logging if needed:
ChipLogDetail(NotSpecified, "Predicted flag set to %s", entryNewValue.predicted.Value() ? "true" : "false");
}
if (!ctx->TariffComponentKeyIDsFeatureMap.insert({ entryNewValue.tariffComponentID, entryFeatures.Raw() }).second)
{
ChipLogError(AppServer, "Duplicate tariffComponentID found");
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
return CHIP_NO_ERROR;
}
CHIP_ERROR ValidateListEntry(const TariffPeriodStruct::Type & entryNewValue, void * aCtx)
{
auto * ctx = static_cast<TariffUpdateCtx *>(aCtx);
std::unordered_set<uint32_t> entryTcIDs;
if (!entryNewValue.label.IsNull())
{
const auto & labelSpan = entryNewValue.label.Value();
if (labelSpan.size() > kDefaultStringValuesMaxBufLength)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
if (labelSpan.empty())
{
ChipLogError(AppServer, "TariffPeriod label must not be empty if present");
return CHIP_ERROR_INVALID_ARGUMENT;
}
}
if (entryNewValue.dayEntryIDs.empty() || entryNewValue.dayEntryIDs.size() > kTariffPeriodItemMaxIDs)
return CHIP_ERROR_INVALID_ARGUMENT;
if (entryNewValue.tariffComponentIDs.empty() || entryNewValue.tariffComponentIDs.size() > kTariffPeriodItemMaxIDs)
return CHIP_ERROR_INVALID_ARGUMENT;
// Checks that dayEntryIDs references has no duplicates among another TP entries
if (CommonUtilities::HasDuplicateIDs(entryNewValue.dayEntryIDs, ctx->TariffPeriodsDayEntryIDs))
{
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
// Checks that the current period item has no duplicated tariffComponentIDs
if (CommonUtilities::HasDuplicateIDs(entryNewValue.tariffComponentIDs, entryTcIDs))
{
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
// ctx->TariffPeriodsDayEntryIDs.merge(entryDeIDs);
ctx->TariffPeriodsTariffComponentIDs.merge(entryTcIDs);
return CHIP_NO_ERROR;
}
template <typename T>
CHIP_ERROR CTC_BaseDataClass<T>::ValidateNewValue()
{
// Log error since this base implementation doesn't do anything meaningful
ChipLogError(DataManagement, "CopyData() called on base class - this should be overridden!");
// Return appropriate error code
return CHIP_ERROR_NOT_IMPLEMENTED;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<Globals::TariffUnitEnum>>::ValidateNewValue()
{
VerifyOrReturnError(EnsureKnownEnumValue(GetNewValueRef().Value()) != Globals::TariffUnitEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<int16_t>>::ValidateNewValue()
{
if (mAttrId == Clusters::CommodityTariff::Attributes::DefaultRandomizationOffset::Id)
{
if (CommonUtilities::HasFeatureInCtx(static_cast<TariffUpdateCtx *>(mAuxData), CommodityTariff::Feature::kRandomization))
{
ChipLogDetail(NotSpecified, "DefaultRandomizationOffset: %u", GetNewValueRef().Value());
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<uint32_t>>::ValidateNewValue()
{
if (mAttrId == Clusters::CommodityTariff::Attributes::StartDate::Id)
{
const auto & newValue = GetNewValueRef();
if (!newValue.IsNull() && newValue.Value() != 0)
{
VerifyOrReturnError((static_cast<TariffUpdateCtx *>(mAuxData)->TariffUpdateTimestamp <= newValue.Value()),
CHIP_ERROR_INVALID_ARGUMENT);
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DayEntryRandomizationTypeEnum>>::ValidateNewValue()
{
VerifyOrReturnError(EnsureKnownEnumValue(GetNewValueRef().Value()) != DayEntryRandomizationTypeEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<TariffInformationStruct::Type>>::ValidateNewValue()
{
if (GetNewValueRef().IsNull())
{
return CHIP_NO_ERROR;
}
const auto & newValue = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
// Validate string lengths (if fields are provided)
VerifyOrReturnError(newValue.tariffLabel.IsNull() || newValue.tariffLabel.Value().size() <= kTariffInfoMaxLabelLength,
CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(newValue.providerName.IsNull() || newValue.providerName.Value().size() <= kTariffInfoMaxProviderLength,
CHIP_ERROR_INVALID_ARGUMENT);
// Validate enum values
if (!newValue.blockMode.IsNull())
{
VerifyOrReturnError(EnsureKnownEnumValue(newValue.blockMode.Value()) != BlockModeEnum::kUnknownEnumValue,
CHIP_ERROR_INVALID_ARGUMENT);
ctx->blockMode = newValue.blockMode.Value();
}
else
{
ctx->blockMode = BlockModeEnum::kNoBlock;
}
// Handle currency validation based on pricing feature
const bool pricingEnabled = CommonUtilities::HasFeatureInCtx(ctx, CommodityTariff::Feature::kPricing);
if (pricingEnabled)
{
// Pricing enabled - currency must be provided (null or non-null)
VerifyOrReturnError(newValue.currency.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
// If non-null, validate contents
if (!newValue.currency.Value().IsNull())
{
const auto & currency = newValue.currency.Value().Value();
VerifyOrReturnError(currency.currency < kMaxCurrencyValue, CHIP_ERROR_INVALID_ARGUMENT);
}
// Else: null is valid when pricing enabled
}
else
{
// Pricing disabled - currency must not be provided at all
VerifyOrReturnError(!newValue.currency.HasValue(), CHIP_ERROR_INVALID_ARGUMENT);
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayEntryStruct::Type>>>::ValidateNewValue()
{
// Required field check
if (GetNewValueRef().IsNull())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
const auto & newList = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
// Validate list length
if (newList.size() == 0 || newList.size() > kDayEntriesAttrMaxLength)
{
ChipLogError(AppServer, "Incorrect DayEntries length");
return CHIP_ERROR_INVALID_LIST_LENGTH;
}
// Validate each entry
for (const auto & item : newList)
{
// Validate entry contents
CHIP_ERROR entryErr = ValidateListEntry(item, ctx);
if (entryErr != CHIP_NO_ERROR)
{
return entryErr;
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayPatternStruct::Type>>>::ValidateNewValue()
{
if (GetNewValueRef().IsNull())
{
return CHIP_NO_ERROR; // Assuming null is valid for day patterns
}
const auto & newList = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
uint8_t daysOfWeekMask = 0;
// Validate list length
if (newList.size() == 0 || newList.size() > kDayPatternsAttrMaxLength)
{
ChipLogError(AppServer, "Incorrect dayPatterns length");
return CHIP_ERROR_INVALID_LIST_LENGTH;
}
// Validate each pattern
for (const auto & item : newList)
{
if (!ctx->DayPatternKeyIDs.insert(item.dayPatternID).second)
{
ChipLogError(AppServer, "Duplicate dayPatternID found");
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
daysOfWeekMask |= item.daysOfWeek.Raw();
CHIP_ERROR entryErr = ValidateListEntry(item, ctx);
if (entryErr != CHIP_NO_ERROR)
{
return entryErr;
}
}
// Validate week coverage
const bool isValidSingleRotatingDay = (!daysOfWeekMask && !newList.empty());
const bool isValidFullWeekCoverage = (daysOfWeekMask == kFullWeekMask);
if (!(isValidSingleRotatingDay || isValidFullWeekCoverage))
{
ChipLogError(AppServer, "Invalid day pattern coverage");
return CHIP_ERROR_INVALID_ARGUMENT;
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffComponentStruct::Type>>>::ValidateNewValue()
{
// Required field check
if (GetNewValueRef().IsNull())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
const auto & newList = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
// Validate list length
if (newList.size() == 0 || newList.size() > kTariffComponentsAttrMaxLength)
{
return CHIP_ERROR_INVALID_LIST_LENGTH;
}
// Validate each component
for (const auto & item : newList)
{
CHIP_ERROR entryErr = ValidateListEntry(item, ctx);
if (entryErr != CHIP_NO_ERROR)
{
return entryErr;
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffPeriodStruct::Type>>>::ValidateNewValue()
{
// Required field check
if (GetNewValueRef().IsNull())
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
const auto & newList = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
// Validate list length
if (newList.size() == 0 || newList.size() > kTariffPeriodsAttrMaxLength)
{
ChipLogError(AppServer, "Incorrect TariffPeriods length");
return CHIP_ERROR_INVALID_LIST_LENGTH;
}
// Validate each period
for (const auto & item : newList)
{
CHIP_ERROR entryErr = ValidateListEntry(item, ctx);
if (entryErr != CHIP_NO_ERROR)
{
return entryErr;
}
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayStruct::Type>>>::ValidateNewValue()
{
// Early return for null case (valid)
if (GetNewValueRef().IsNull())
{
return CHIP_NO_ERROR;
}
const auto & newList = GetNewValueRef().Value();
auto * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
uint32_t previousDate = 0;
// Validate list length
if (newList.size() == 0 || newList.size() > kIndividualDaysAttrMaxLength)
{
ChipLogError(AppServer, "Incorrect IndividualDays length");
return CHIP_ERROR_INVALID_LIST_LENGTH;
}
// Validate each item
for (const auto & item : newList)
{
// Check date ordering
if (item.date <= previousDate)
{
ChipLogError(AppServer, "IndividualDays must be ordered by date");
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Validate day type enum
if (EnsureKnownEnumValue(item.dayType) == DayTypeEnum::kUnknownEnumValue)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Validate dayEntryIDs
if (item.dayEntryIDs.empty() || item.dayEntryIDs.size() > kDayStructItemMaxDayEntryIDs)
{
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Check for duplicates
if (CommonUtilities::HasDuplicateIDs(item.dayEntryIDs, ctx->IndividualDaysDayEntryIDs))
{
ChipLogError(AppServer, "Duplicate dayEntryID found");
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
previousDate = item.date;
}
return CHIP_NO_ERROR;
}
template <>
CHIP_ERROR CTC_BaseDataClass<DataModel::Nullable<DataModel::List<CalendarPeriodStruct::Type>>>::ValidateNewValue()
{
// If calendar is null, it's always valid
if (GetNewValueRef().IsNull())
{
return CHIP_NO_ERROR;
}
auto & newList = GetNewValueRef().Value();
bool isFirstItem = true;
Nullable<uint32_t> previousStartDate;
TariffUpdateCtx * ctx = static_cast<TariffUpdateCtx *>(mAuxData);
std::unordered_set<uint32_t> & CalendarPeriodsDayPatternIDs = ctx->CalendarPeriodsDayPatternIDs;
auto & tariffStartDate = ctx->TariffStartTimestamp;
if (tariffStartDate.IsNull())
{
// StartDate is Null - tariff is unavailable;
return CHIP_ERROR_INVALID_ARGUMENT;
}
for (const auto & item : newList)
{
// Validate dayPatternIDs count
if (item.dayPatternIDs.empty() || item.dayPatternIDs.size() > kCalendarPeriodItemMaxDayPatternIDs)
{
ChipLogError(AppServer, "DayPatternIDs count must be between 1 and %" PRIu32,
(uint32_t) kCalendarPeriodItemMaxDayPatternIDs);
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Check for duplicate dayPatternIDs
if (CommonUtilities::HasDuplicateIDs(item.dayPatternIDs, CalendarPeriodsDayPatternIDs))
{
ChipLogError(AppServer, "Duplicate dayPatternID found in CalendarPeriods");
return CHIP_ERROR_DUPLICATE_KEY_ID;
}
// Special handling for first item
if (isFirstItem)
{
// Case 1: Invalid - tariff start date is 0 but item has a positive start date
if (tariffStartDate.Value() == 0 && (!item.startDate.IsNull() && item.startDate.Value() > 0))
{
ChipLogError(AppServer,
"The first StartDate in CalendarPeriods can't have a value if the StartDate of tariff is 0");
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Case 2: Invalid - tariff has positive start date but item has no valid start date
if (tariffStartDate.Value() > 0 && (item.startDate.IsNull() || item.startDate.Value() == 0))
{
ChipLogError(AppServer,
"The first StartDate in CalendarPeriods can't be not set if the StartDate of tariff is specified");
return CHIP_ERROR_INVALID_ARGUMENT;
}
previousStartDate = item.startDate;
isFirstItem = false;
// First item can have null StartDate
continue;
}
// Subsequent items must have non-null StartDate
if (item.startDate.IsNull())
{
ChipLogError(AppServer, "Only first CalendarPeriodStruct can have null StartDate");
return CHIP_ERROR_INVALID_ARGUMENT;
}
// Validate StartDate ordering
if (!previousStartDate.IsNull() && !item.startDate.IsNull() && item.startDate.Value() <= previousStartDate.Value())
{
ChipLogError(AppServer, "CalendarPeriodStructs must be in increasing StartDate order");
return CHIP_ERROR_INVALID_ARGUMENT;
}
previousStartDate = item.startDate;
}
return CHIP_NO_ERROR;
}
// Define the template method
template <typename T>
void CTC_BaseDataClass<T>::CleanupStruct(StructType & aValue)
{
// Implementation for all derived classes
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<TariffInformationStruct::Type>>::CleanupStruct(StructType & aValue)
{
if (!aValue.tariffLabel.IsNull() && aValue.tariffLabel.Value().data())
{
Platform::MemoryFree(const_cast<char *>(aValue.tariffLabel.Value().data()));
aValue.tariffLabel.SetNull();
}
if (!aValue.providerName.IsNull() && aValue.providerName.Value().data())
{
Platform::MemoryFree(const_cast<char *>(aValue.providerName.Value().data()));
aValue.providerName.SetNull();
}
aValue.currency.ClearValue();
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayEntryStruct::Type>>>::CleanupStruct(StructType & aValue)
{
aValue.duration.ClearValue();
aValue.randomizationOffset.ClearValue();
aValue.randomizationType.ClearValue();
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayPatternStruct::Type>>>::CleanupStruct(StructType & aValue)
{
CommonUtilities::CleanUpIDs(aValue.dayEntryIDs);
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffComponentStruct::Type>>>::CleanupStruct(StructType & aValue)
{
if (aValue.label.HasValue() && !aValue.label.Value().IsNull())
{
MemoryFree(const_cast<char *>(aValue.label.Value().Value().data()));
aValue.label.ClearValue();
}
aValue.price.ClearValue();
aValue.friendlyCredit.ClearValue();
aValue.auxiliaryLoad.ClearValue();
aValue.peakPeriod.ClearValue();
aValue.powerThreshold.ClearValue();
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<TariffPeriodStruct::Type>>>::CleanupStruct(StructType & aValue)
{
if (!aValue.label.IsNull() && aValue.label.Value().data())
{
Platform::MemoryFree(const_cast<char *>(aValue.label.Value().data()));
aValue.label.SetNull();
}
CommonUtilities::CleanUpIDs(aValue.dayEntryIDs);
CommonUtilities::CleanUpIDs(aValue.tariffComponentIDs);
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<DayStruct::Type>>>::CleanupStruct(StructType & aValue)
{
CommonUtilities::CleanUpIDs(aValue.dayEntryIDs);
}
template <>
void CTC_BaseDataClass<DataModel::Nullable<DataModel::List<CalendarPeriodStruct::Type>>>::CleanupStruct(StructType & aValue)
{
CommonUtilities::CleanUpIDs(aValue.dayPatternIDs);
}
} // namespace CommodityTariffAttrsDataMgmt
} // namespace app
} // namespace chip