blob: 53c998b21f5e7d6badd4e715026aaba653659c12 [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.
*/
#pragma once
#include <lib/support/CompileTimeString.h>
#include <lib/support/EnforceFormat.h>
#include <lib/support/Span.h>
#include <lib/support/TypeTraits.h>
#include <memory>
#include <optional>
extern "C" {
#include <libubox/blobmsg.h>
}
namespace chip {
namespace ubus {
namespace detail {
// Exposes type id and blobmsg_{get,add}_* functions for a type
template <typename T>
struct BlobMsgType
{
static_assert(!TemplatedTrueType<T>(), "Type is not supported by blob_msg");
static constexpr enum blobmsg_type id = BLOBMSG_TYPE_UNSPEC;
static int get(blob_attr * attr, T & value) { return -1; }
static int add(blob_buf * buf, const char * name, T value) { return -1; }
};
// Specializations for types natively supported by blobmsg_get_* and blobmsg_add_*
// clang-format off
#define _CHIP_BLOBMSG_NATIVE_TYPE(_T, _id, _get, _add) \
template <> struct BlobMsgType<_T> \
{ \
static constexpr enum blobmsg_type id = _id; \
static int get(blob_attr * attr, _T & value) { value = _get(attr); return 0; } \
static int add(blob_buf * buf, const char * name, _T value) { return _add(buf, name, value); } \
}
_CHIP_BLOBMSG_NATIVE_TYPE(const char *, BLOBMSG_TYPE_STRING, blobmsg_get_string, blobmsg_add_string);
_CHIP_BLOBMSG_NATIVE_TYPE(bool, BLOBMSG_TYPE_BOOL, blobmsg_get_u8, blobmsg_add_u8);
_CHIP_BLOBMSG_NATIVE_TYPE(double, BLOBMSG_TYPE_DOUBLE, blobmsg_get_double, blobmsg_add_double);
_CHIP_BLOBMSG_NATIVE_TYPE(uint8_t, BLOBMSG_TYPE_INT8, blobmsg_get_u8, blobmsg_add_u8);
_CHIP_BLOBMSG_NATIVE_TYPE(int8_t, BLOBMSG_TYPE_INT8, blobmsg_get_u8, blobmsg_add_u8);
_CHIP_BLOBMSG_NATIVE_TYPE(uint16_t, BLOBMSG_TYPE_INT16, blobmsg_get_u16, blobmsg_add_u16);
_CHIP_BLOBMSG_NATIVE_TYPE(int16_t, BLOBMSG_TYPE_INT16, blobmsg_get_u16, blobmsg_add_u16);
_CHIP_BLOBMSG_NATIVE_TYPE(uint32_t, BLOBMSG_TYPE_INT32, blobmsg_get_u32, blobmsg_add_u32);
_CHIP_BLOBMSG_NATIVE_TYPE(int32_t, BLOBMSG_TYPE_INT32, blobmsg_get_u32, blobmsg_add_u32);
_CHIP_BLOBMSG_NATIVE_TYPE(uint64_t, BLOBMSG_TYPE_INT64, blobmsg_get_u64, blobmsg_add_u64);
_CHIP_BLOBMSG_NATIVE_TYPE(int64_t, BLOBMSG_TYPE_INT64, blobmsg_get_u64, blobmsg_add_u64);
#undef _CHIP_BLOBMSG_NATIVE_TYPE
// clang-format on
// Specialization for mapping ByteSpan to a hex-encoded BLOBMSG_TYPE_STRING
// Note that decoding is performed in-place, i.e. is destructive.
template <>
struct BlobMsgType<ByteSpan>
{
static constexpr enum blobmsg_type id = BLOBMSG_TYPE_STRING;
static int get(blob_attr * attr, ByteSpan & value);
static int add(blob_buf * buf, const char * name, ByteSpan const & value);
};
// Non-optional value wrapper - similar to std::optional but always contains a value
template <typename T>
struct NonOptional
{
T & value() { return mValue; }
const T & value() const { return mValue; }
operator T &() { return mValue; }
operator const T &() const { return mValue; }
NonOptional & operator=(const T & val)
{
mValue = val;
return *this;
}
void reset() { mValue = T{}; }
private:
T mValue{};
};
// Name-independent parts of BlobMsgField
template <typename T, bool Required>
struct BlobMsgFieldBase : public std::conditional_t<Required, NonOptional<T>, std::optional<T>>
{
using Base = std::conditional_t<Required, NonOptional<T>, std::optional<T>>;
using Impl = BlobMsgType<T>;
using Base::Base;
using Base::operator=;
using ValueType = T;
static constexpr bool required() { return Required; }
bool Decode(blob_attr * attr)
{
if (!attr)
{
if constexpr (required())
{
return false;
}
else
{
this->reset();
return true;
}
}
T value;
VerifyOrReturnValue(Impl::get(attr, value) == 0, false);
Base::operator=(value);
return true;
}
};
} // namespace detail
// A field that can be read from or written in blob_msg format.
// The field type captures the field name as a compile-time string,
// so a matching blobmsg_policy can be generated automatically.
//
// By default fields are optional, i.e. derive from std::optional.
// Passing Required=true (or using BlobMsgRequiredField) creates makes
// the field non-optional. BlobMsgParse() will fail is any required
// fields are missing.
template <typename T, typename NameCTST, bool Required = false>
struct BlobMsgField final : public detail::BlobMsgFieldBase<T, Required>
{
using Base = detail::BlobMsgFieldBase<T, Required>;
using Base::Base;
using Base::operator=;
using typename Base::Impl;
static constexpr const char * name() { return NameCTST::c_str(); }
static constexpr blobmsg_policy policy() { return { .name = name(), .type = Impl::id }; }
};
// Alias for a required BlobMsgField
template <typename T, typename NameCTST>
using BlobMsgRequiredField = BlobMsgField<T, NameCTST, true>;
// Parses the specified fields using blobmsg_parse() and returns true on success.
// Fails (i.e. returns false) if blobmsg_parse() fails, any required field is not
// present, or decoding of any field fails. Note that fields that are present with
// the wrong type are ignored by blobmsg_parse() and not treated as an error.
//
// Note: This function accepts both plain and extended blob_attrs, i.e. works with
// both message payloads and nested tables.
template <typename... BlobMsgFields>
bool BlobMsgParse(blob_attr * attr, BlobMsgFields &... fields)
{
static constexpr blobmsg_policy policy[] = { BlobMsgFields::policy()... };
blob_attr * values[sizeof...(BlobMsgFields)];
VerifyOrReturnValue(!blobmsg_parse_attr(policy, sizeof...(BlobMsgFields), values, attr), false);
return [&]<size_t... Is>(std::index_sequence<Is...>) {
return (fields.Decode(values[Is]) && ...);
}(std::index_sequence_for<BlobMsgFields...>{});
}
// A RAII helper for automatically closing nested tables / arrays with BlobMsgBuf.
class BlobMsgCookie
{
public:
BlobMsgCookie(blob_buf * buf, void * cookie) : mCookie(cookie, Deleter{ buf }) {}
/* implicit */ operator bool() const { return mCookie.get() != nullptr; }
private:
struct Deleter
{
blob_buf * mBuf;
void operator()(void * cookie) { blob_nest_end(mBuf, cookie); }
};
std::unique_ptr<void, Deleter> mCookie;
};
// A thin wrapper around blob_buf with automatic resource management
struct BlobMsgBuf final : public blob_buf
{
BlobMsgBuf() : blob_buf{} { Clear(); }
~BlobMsgBuf() { blob_buf_free(this); }
// Returns true if the buffer has encounter an allocation error.
// Once in an error state any subsequent add operations will have no effect.
// Individual operations return true on success.
bool HasError() const { return this->head == nullptr; }
// Clears the buffer and resets the error state.
bool Clear() { return Check(blob_buf_init(this, 0)); }
///// Encoding methods with explicit field names
template <typename T>
bool Add(const char * name, T value)
{
return !HasError() && Check(detail::BlobMsgType<T>::add(this, name, value));
}
bool AddFormat(const char * name, const char * format, ...) ENFORCE_FORMAT(3, 4);
BlobMsgCookie AddArray(const char * name) { return AddNested(name, /* array = */ true); }
BlobMsgCookie AddTable(const char * name) { return AddNested(name, /* array = */ false); }
///// Encoding methods based on BlobMsgField types or instances
// Adds a value, with the name given by a BlogMsgField type, which must be an explicit template argument.
// Example: buf.Add<ErrorField>(0);
template <typename BlobMsgField> // explicit
bool Add(typename BlobMsgField::ValueType value)
{
return Add(BlobMsgField::name(), value);
}
// Adds a BlogMsgField instance. If the field is optional and absent, nothing is encoded.
template <typename BlobMsgField, typename = typename BlobMsgField::ValueType>
bool Add(BlobMsgField const & field)
{
if constexpr (field.required())
{
return Add(field.name(), field.value());
}
else
{
return AddOptional(field.name(), field);
}
}
private:
BlobMsgCookie AddNested(const char * name, bool array)
{
return BlobMsgCookie(this, !HasError() ? blobmsg_open_nested(this, name, array) : nullptr);
}
template <typename T>
bool AddOptional(const char * name, std::optional<T> const & optional)
{
return !HasError() && (!optional.has_value() || Check(detail::BlobMsgType<T>::add(this, name, optional.value())));
}
bool Check(int status)
{
if (status != 0)
{
this->head = nullptr;
return false;
}
return true;
}
};
} // namespace ubus
} // namespace chip