/*
 * Copyright 2020 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1-or-later
 */

#include "config.h"

#include <gio/gio.h>

#include "fwupd-codec.h"
#include "fwupd-common-private.h"
#include "fwupd-enums-private.h"
#include "fwupd-error.h"
#include "fwupd-json-array.h"
#include "fwupd-security-attr-private.h"
#include "fwupd-variant.h"

/**
 * FwupdSecurityAttr:
 *
 * A Host Security ID attribute that represents something that was measured.
 */

static void
fwupd_security_attr_finalize(GObject *object);

typedef struct {
	gchar *appstream_id;
	GPtrArray *obsoletes;
	GPtrArray *guids;
	GHashTable *metadata; /* (nullable) */
	gchar *name;
	gchar *title;
	gchar *description;
	gchar *plugin;
	gchar *fwupd_version;
	gchar *url;
	guint64 created;
	FwupdSecurityAttrLevel level;
	FwupdSecurityAttrResult result;
	FwupdSecurityAttrResult result_fallback;
	FwupdSecurityAttrResult result_success;
	FwupdSecurityAttrFlags flags;
	gchar *bios_setting_id;
	gchar *bios_setting_target_value;
	gchar *bios_setting_current_value;
	gchar *kernel_current_value;
	gchar *kernel_target_value;
} FwupdSecurityAttrPrivate;

static void
fwupd_security_attr_codec_iface_init(FwupdCodecInterface *iface);

G_DEFINE_TYPE_EXTENDED(FwupdSecurityAttr,
		       fwupd_security_attr,
		       G_TYPE_OBJECT,
		       0,
		       G_ADD_PRIVATE(FwupdSecurityAttr)
			   G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC,
						 fwupd_security_attr_codec_iface_init));

#define GET_PRIVATE(o) (fwupd_security_attr_get_instance_private(o))

/**
 * fwupd_security_attr_flag_from_string:
 * @flag: (nullable): a string, e.g. `success`
 *
 * Converts a string to an enumerated flag.
 *
 * Returns: enumerated value
 *
 * Since: 1.7.1
 **/
FwupdSecurityAttrFlags
fwupd_security_attr_flag_from_string(const gchar *flag)
{
	if (g_strcmp0(flag, "success") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_SUCCESS;
	if (g_strcmp0(flag, "obsoleted") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_OBSOLETED;
	if (g_strcmp0(flag, "missing-data") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA;
	if (g_strcmp0(flag, "runtime-updates") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES;
	if (g_strcmp0(flag, "runtime-attestation") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION;
	if (g_strcmp0(flag, "runtime-issue") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE;
	if (g_strcmp0(flag, "action-contact-oem") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONTACT_OEM;
	if (g_strcmp0(flag, "action-config-fw") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_FW;
	if (g_strcmp0(flag, "action-config-os") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_ACTION_CONFIG_OS;
	if (g_strcmp0(flag, "can-fix") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_CAN_FIX;
	if (g_strcmp0(flag, "can-undo") == 0)
		return FWUPD_SECURITY_ATTR_FLAG_CAN_UNDO;
	return FWUPD_SECURITY_ATTR_FLAG_NONE;
}

/**
 * fwupd_security_attr_result_from_string:
 * @result: (nullable): a string, e.g. `not-encrypted`
 *
 * Converts a string to an enumerated result.
 *
 * Returns: enumerated value
 *
 * Since: 1.7.1
 **/
FwupdSecurityAttrResult
fwupd_security_attr_result_from_string(const gchar *result)
{
	if (g_strcmp0(result, "valid") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_VALID;
	if (g_strcmp0(result, "not-valid") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_VALID;
	if (g_strcmp0(result, "enabled") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_ENABLED;
	if (g_strcmp0(result, "not-enabled") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED;
	if (g_strcmp0(result, "locked") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_LOCKED;
	if (g_strcmp0(result, "not-locked") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_LOCKED;
	if (g_strcmp0(result, "encrypted") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_ENCRYPTED;
	if (g_strcmp0(result, "not-encrypted") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED;
	if (g_strcmp0(result, "tainted") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_TAINTED;
	if (g_strcmp0(result, "not-tainted") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_TAINTED;
	if (g_strcmp0(result, "found") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_FOUND;
	if (g_strcmp0(result, "not-found") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND;
	if (g_strcmp0(result, "supported") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_SUPPORTED;
	if (g_strcmp0(result, "not-supported") == 0)
		return FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED;
	return FWUPD_SECURITY_ATTR_RESULT_UNKNOWN;
}

/**
 * fwupd_security_attr_flag_to_suffix:
 * @flag: security attribute flags, e.g. %FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES
 *
 * Returns the string suffix for the flag.
 *
 * Returns: string, or %NULL
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_flag_to_suffix(FwupdSecurityAttrFlags flag)
{
	if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_UPDATES)
		return "U";
	if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ATTESTATION)
		return "A";
	if (flag == FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE)
		return "!";
	return NULL;
}

/**
 * fwupd_security_attr_get_bios_setting_id:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the #FwupdBiosSetting that can be used to improve this
 * #FwupdSecurityAttr.
 *
 * Returns: The unique ID used for #FwupdBiosSetting or NULL
 *
 * Since: 1.8.4
 **/
const gchar *
fwupd_security_attr_get_bios_setting_id(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->bios_setting_id;
}

/**
 * fwupd_security_attr_set_bios_setting_id:
 * @self: a #FwupdSecurityAttr
 * @id: (nullable): Unique identifier used for #FwupdBiosSetting
 *
 * Sets the #FwupdBiosSetting that can be used to improve this
 * #FwupdSecurityAttr.
 *
 * Since: 1.8.4
 **/
void
fwupd_security_attr_set_bios_setting_id(FwupdSecurityAttr *self, const gchar *id)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	if (priv->bios_setting_id == id)
		return;
	g_free(priv->bios_setting_id);
	priv->bios_setting_id = g_strdup(id);
}

/**
 * fwupd_security_attr_get_obsoletes:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the list of attribute obsoletes. The obsoleted attributes will not
 * contribute to the calculated HSI value or be visible in command line tools.
 *
 * Returns: (element-type utf8) (transfer none): the obsoletes, which may be empty
 *
 * Since: 1.5.0
 **/
GPtrArray *
fwupd_security_attr_get_obsoletes(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->obsoletes;
}

/**
 * fwupd_security_attr_add_obsolete:
 * @self: a #FwupdSecurityAttr
 * @appstream_id: the appstream_id or plugin name
 *
 * Adds an attribute appstream_id to obsolete. The obsoleted attribute will not
 * contribute to the calculated HSI value or be visible in command line tools.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_add_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	g_return_if_fail(appstream_id != NULL);
	if (fwupd_security_attr_has_obsolete(self, appstream_id))
		return;
	g_ptr_array_add(priv->obsoletes, g_strdup(appstream_id));
}

/**
 * fwupd_security_attr_has_obsolete:
 * @self: a #FwupdSecurityAttr
 * @appstream_id: the attribute appstream_id
 *
 * Finds out if the attribute obsoletes a specific appstream_id.
 *
 * Returns: %TRUE if the self matches
 *
 * Since: 1.5.0
 **/
gboolean
fwupd_security_attr_has_obsolete(FwupdSecurityAttr *self, const gchar *appstream_id)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE);
	g_return_val_if_fail(appstream_id != NULL, FALSE);
	for (guint i = 0; i < priv->obsoletes->len; i++) {
		const gchar *obsolete_tmp = g_ptr_array_index(priv->obsoletes, i);
		if (g_strcmp0(obsolete_tmp, appstream_id) == 0)
			return TRUE;
	}
	return FALSE;
}

/**
 * fwupd_security_attr_get_guids:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the list of attribute GUIDs. The GUID values will not modify the calculated HSI value.
 *
 * Returns: (element-type utf8) (transfer none): the GUIDs, which may be empty
 *
 * Since: 1.7.0
 **/
GPtrArray *
fwupd_security_attr_get_guids(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->guids;
}

/**
 * fwupd_security_attr_add_guid:
 * @self: a #FwupdSecurityAttr
 * @guid: (not nullable): the GUID
 *
 * Adds a device GUID to the attribute. This indicates the GUID in some way contributed to the
 * result decided.
 *
 * Since: 1.7.0
 **/
void
fwupd_security_attr_add_guid(FwupdSecurityAttr *self, const gchar *guid)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	g_return_if_fail(fwupd_guid_is_valid(guid));
	if (fwupd_security_attr_has_guid(self, guid))
		return;
	g_ptr_array_add(priv->guids, g_strdup(guid));
}

/**
 * fwupd_security_attr_add_guids:
 * @self: a #FwupdSecurityAttr
 * @guids: (element-type utf8): the GUIDs
 *
 * Adds device GUIDs to the attribute. This indicates the GUIDs in some way contributed to the
 * result decided.
 *
 * Since: 1.7.0
 **/
void
fwupd_security_attr_add_guids(FwupdSecurityAttr *self, GPtrArray *guids)
{
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	g_return_if_fail(guids != NULL);
	for (guint i = 0; i < guids->len; i++) {
		const gchar *guid = g_ptr_array_index(guids, i);
		fwupd_security_attr_add_guid(self, guid);
	}
}

/**
 * fwupd_security_attr_has_guid:
 * @self: a #FwupdSecurityAttr
 * @guid: the attribute guid
 *
 * Finds out if a specific GUID was added to the attribute.
 *
 * Returns: %TRUE if the self matches
 *
 * Since: 1.7.0
 **/
gboolean
fwupd_security_attr_has_guid(FwupdSecurityAttr *self, const gchar *guid)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE);
	g_return_val_if_fail(guid != NULL, FALSE);
	for (guint i = 0; i < priv->guids->len; i++) {
		const gchar *guid_tmp = g_ptr_array_index(priv->guids, i);
		if (g_strcmp0(guid_tmp, guid) == 0)
			return TRUE;
	}
	return FALSE;
}

/**
 * fwupd_security_attr_get_appstream_id:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the AppStream ID.
 *
 * Returns: the AppStream ID, or %NULL if unset
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_get_appstream_id(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->appstream_id;
}

/**
 * fwupd_security_attr_set_appstream_id:
 * @self: a #FwupdSecurityAttr
 * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard`
 *
 * Sets the AppStream ID.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_appstream_id(FwupdSecurityAttr *self, const gchar *appstream_id)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->appstream_id, appstream_id) == 0)
		return;

	/* sanity check */
	if (appstream_id != NULL && !g_str_has_prefix(appstream_id, "org.fwupd.hsi."))
		g_critical("HSI attributes need to have a 'org.fwupd.hsi.' prefix");

	g_free(priv->appstream_id);
	priv->appstream_id = g_strdup(appstream_id);
}

/**
 * fwupd_security_attr_get_url:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the attribute URL.
 *
 * Returns: the attribute result, or %NULL if unset
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_get_url(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->url;
}

/**
 * fwupd_security_attr_set_name:
 * @self: a #FwupdSecurityAttr
 * @name: (nullable): the attribute name
 *
 * Sets the attribute name.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_name(FwupdSecurityAttr *self, const gchar *name)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->name, name) == 0)
		return;

	g_free(priv->name);
	priv->name = g_strdup(name);
}

/**
 * fwupd_security_attr_get_bios_setting_target_value:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the value that when written to an attribute would activate it or satisfy
 * a security requirement.
 *
 * Returns: the target value of the attribute.
 *
 * Since: 1.8.4
 **/
const gchar *
fwupd_security_attr_get_bios_setting_target_value(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->bios_setting_target_value;
}

/**
 * fwupd_security_attr_set_bios_setting_target_value:
 * @self: a #FwupdSecurityAttr
 * @value: (nullable): The string to set target value to
 *
 * Sets the string used for the target value of an attribute.
 *
 * Since: 1.8.4
 **/
void
fwupd_security_attr_set_bios_setting_target_value(FwupdSecurityAttr *self, const gchar *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	/* not changed */
	if (g_strcmp0(priv->bios_setting_target_value, value) == 0)
		return;

	g_free(priv->bios_setting_target_value);
	priv->bios_setting_target_value = g_strdup(value);
}

/**
 * fwupd_security_attr_get_bios_setting_current_value:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the current value of the BIOS setting that can be changed.
 *
 * Returns: the current value of the attribute.
 *
 * Since: 1.8.4
 **/
const gchar *
fwupd_security_attr_get_bios_setting_current_value(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->bios_setting_current_value;
}

/**
 * fwupd_security_attr_set_bios_setting_current_value:
 * @self: a #FwupdSecurityAttr
 * @value: (nullable): The string to set current value to
 *
 * Sets the current value of the BIOS setting that can be changed.
 *
 * Since: 1.8.4
 **/
void
fwupd_security_attr_set_bios_setting_current_value(FwupdSecurityAttr *self, const gchar *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	/* not changed */
	if (g_strcmp0(priv->bios_setting_current_value, value) == 0)
		return;

	g_free(priv->bios_setting_current_value);
	priv->bios_setting_current_value = g_strdup(value);
}

/**
 * fwupd_security_attr_get_kernel_current_value:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the current value of the BIOS setting that can be changed.
 *
 * Returns: the current value of the attribute.
 *
 * Since: 1.9.6
 **/
const gchar *
fwupd_security_attr_get_kernel_current_value(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->kernel_current_value;
}

/**
 * fwupd_security_attr_set_kernel_current_value:
 * @self: a #FwupdSecurityAttr
 * @value: (nullable): The string to set current value to
 *
 * Sets the current value of the BIOS setting that can be changed.
 *
 * Since: 1.9.6
 **/
void
fwupd_security_attr_set_kernel_current_value(FwupdSecurityAttr *self, const gchar *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	/* not changed */
	if (g_strcmp0(priv->kernel_current_value, value) == 0)
		return;

	g_free(priv->kernel_current_value);
	priv->kernel_current_value = g_strdup(value);
}

/**
 * fwupd_security_attr_get_kernel_target_value:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the target value of the kernel setting that can be changed.
 *
 * Returns: the current value of the attribute.
 *
 * Since: 1.9.6
 **/
const gchar *
fwupd_security_attr_get_kernel_target_value(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->kernel_target_value;
}

/**
 * fwupd_security_attr_set_kernel_target_value:
 * @self: a #FwupdSecurityAttr
 * @value: (nullable): The string to set current value to
 *
 * Sets the target value of the kernel setting that can be changed.
 *
 * Since: 1.9.6
 **/
void
fwupd_security_attr_set_kernel_target_value(FwupdSecurityAttr *self, const gchar *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	/* not changed */
	if (g_strcmp0(priv->kernel_target_value, value) == 0)
		return;

	g_free(priv->kernel_target_value);
	priv->kernel_target_value = g_strdup(value);
}

/**
 * fwupd_security_attr_set_title:
 * @self: a #FwupdSecurityAttr
 * @title: (nullable): the attribute title
 *
 * Sets the attribute title.
 *
 * Since: 1.8.2
 **/
void
fwupd_security_attr_set_title(FwupdSecurityAttr *self, const gchar *title)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->title, title) == 0)
		return;

	g_free(priv->title);
	priv->title = g_strdup(title);
}

/**
 * fwupd_security_attr_set_description:
 * @self: a #FwupdSecurityAttr
 * @description: (nullable): the attribute description
 *
 * Sets the attribute description.
 *
 * Since: 1.8.2
 **/
void
fwupd_security_attr_set_description(FwupdSecurityAttr *self, const gchar *description)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->description, description) == 0)
		return;

	g_free(priv->description);
	priv->description = g_strdup(description);
}

/**
 * fwupd_security_attr_set_plugin:
 * @self: a #FwupdSecurityAttr
 * @plugin: (nullable): the plugin name
 *
 * Sets the plugin that created the attribute.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_plugin(FwupdSecurityAttr *self, const gchar *plugin)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->plugin, plugin) == 0)
		return;

	g_free(priv->plugin);
	priv->plugin = g_strdup(plugin);
}

/**
 * fwupd_security_attr_set_fwupd_version:
 * @self: a #FwupdSecurityAttr
 * @fwupd_version: (nullable): the fwupd version, e.g. `2.0.7`
 *
 * Sets the fwupd version the attribute was added.
 *
 * Since: 2.0.7
 **/
void
fwupd_security_attr_set_fwupd_version(FwupdSecurityAttr *self, const gchar *fwupd_version)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->fwupd_version, fwupd_version) == 0)
		return;

	g_free(priv->fwupd_version);
	priv->fwupd_version = g_strdup(fwupd_version);
}

/**
 * fwupd_security_attr_set_url:
 * @self: a #FwupdSecurityAttr
 * @url: (nullable): the attribute URL
 *
 * Sets the attribute result.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_url(FwupdSecurityAttr *self, const gchar *url)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* not changed */
	if (g_strcmp0(priv->url, url) == 0)
		return;

	g_free(priv->url);
	priv->url = g_strdup(url);
}
/**
 * fwupd_security_attr_get_created:
 * @self: a #FwupdSecurityAttr
 *
 * Gets when the attribute was created.
 *
 * Returns: the UNIX time, or 0 if unset
 *
 * Since: 1.7.1
 **/
guint64
fwupd_security_attr_get_created(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->created;
}

/**
 * fwupd_security_attr_set_created:
 * @self: a #FwupdSecurityAttr
 * @created: the UNIX time
 *
 * Sets when the attribute was created.
 *
 * Since: 1.7.1
 **/
void
fwupd_security_attr_set_created(FwupdSecurityAttr *self, guint64 created)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->created = created;
}

/**
 * fwupd_security_attr_get_name:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the attribute name.
 *
 * Returns: the attribute name, or %NULL if unset
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_get_name(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->name;
}

/**
 * fwupd_security_attr_get_title:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the attribute title, which is typically a two word title.
 *
 * The fwupd client program may be able to get translations for this value using a method call
 * like `dgettext("fwupd",str)`.
 *
 * Returns: the attribute title, or %NULL if unset
 *
 * Since: 1.8.2
 **/
const gchar *
fwupd_security_attr_get_title(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->title;
}

/**
 * fwupd_security_attr_get_description:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the attribute description which is a few lines of prose that normal users will understand.
 *
 * The fwupd client program may be able to get translations for this value using a method call
 * like `dgettext("fwupd",str)`.
 *
 * NOTE: The returned string may contain placeholders such as `$HostVendor$` or `$HostProduct$`
 * and these should be replaced with the values from [method@FwupdClient.get_host_vendor] and
 * [method@FwupdClient.get_host_product].
 *
 * Returns: the attribute description, or %NULL if unset
 *
 * Since: 1.8.2
 **/
const gchar *
fwupd_security_attr_get_description(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->description;
}

/**
 * fwupd_security_attr_get_plugin:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the plugin that created the attribute.
 *
 * Returns: the plugin name, or %NULL if unset
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_get_plugin(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->plugin;
}

/**
 * fwupd_security_attr_get_fwupd_version:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the fwupd version the attribute was added.
 *
 * Returns: the fwupd version, or %NULL if unset
 *
 * Since: 2.0.7
 **/
const gchar *
fwupd_security_attr_get_fwupd_version(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	return priv->fwupd_version;
}

/**
 * fwupd_security_attr_get_flags:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the self flags.
 *
 * Returns: security attribute flags, or 0 if unset
 *
 * Since: 1.5.0
 **/
FwupdSecurityAttrFlags
fwupd_security_attr_get_flags(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->flags;
}

/**
 * fwupd_security_attr_set_flags:
 * @self: a #FwupdSecurityAttr
 * @flags: security attribute flags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED
 *
 * Sets the attribute flags.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_flags(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flags)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->flags = flags;
}

/* copy over the success result if not already set */
static void
fwupd_security_attr_ensure_result(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	if (!fwupd_security_attr_has_flag(self, FWUPD_SECURITY_ATTR_FLAG_SUCCESS))
		return;
	if (fwupd_security_attr_has_flag(self, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA))
		fwupd_security_attr_remove_flag(self, FWUPD_SECURITY_ATTR_FLAG_MISSING_DATA);
	if (priv->result == FWUPD_SECURITY_ATTR_RESULT_UNKNOWN &&
	    priv->result_success != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		g_debug("auto-setting %s result %s",
			priv->appstream_id,
			fwupd_security_attr_result_to_string(priv->result_success));
		priv->result = priv->result_success;
	}
}

/**
 * fwupd_security_attr_add_flag:
 * @self: a #FwupdSecurityAttr
 * @flag: the #FwupdSecurityAttrFlags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED
 *
 * Adds a specific attribute flag to the attribute.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_add_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->flags |= flag;
	fwupd_security_attr_ensure_result(self);
}

/**
 * fwupd_security_attr_remove_flag:
 * @self: a #FwupdSecurityAttr
 * @flag: the #FwupdSecurityAttrFlags, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED
 *
 * Removes a specific attribute flag from the attribute.
 *
 * Since: 1.8.3
 **/
void
fwupd_security_attr_remove_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->flags &= ~flag;
}

/**
 * fwupd_security_attr_has_flag:
 * @self: a #FwupdSecurityAttr
 * @flag: the attribute flag, e.g. %FWUPD_SECURITY_ATTR_FLAG_OBSOLETED
 *
 * Finds if the attribute has a specific attribute flag.
 *
 * Returns: %TRUE if the flag is set
 *
 * Since: 1.5.0
 **/
gboolean
fwupd_security_attr_has_flag(FwupdSecurityAttr *self, FwupdSecurityAttrFlags flag)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), FALSE);
	return (priv->flags & flag) > 0;
}

/**
 * fwupd_security_attr_get_level:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the HSI level.
 *
 * Returns: the security attribute level, or %FWUPD_SECURITY_ATTR_LEVEL_NONE if unset
 *
 * Since: 1.5.0
 **/
FwupdSecurityAttrLevel
fwupd_security_attr_get_level(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->level;
}

/**
 * fwupd_security_attr_set_level:
 * @self: a #FwupdSecurityAttr
 * @level: a security attribute level, e.g. %FWUPD_SECURITY_ATTR_LEVEL_IMPORTANT
 *
 * Sets the HSI level. A @level of %FWUPD_SECURITY_ATTR_LEVEL_NONE is not used
 * for the HSI calculation.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_level(FwupdSecurityAttr *self, FwupdSecurityAttrLevel level)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->level = level;
}

/**
 * fwupd_security_attr_set_result:
 * @self: a #FwupdSecurityAttr
 * @result: a security attribute result, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Sets the optional HSI result. This is required because some attributes may
 * be a "success" when something is `locked` or may be "failed" if `found`.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_set_result(FwupdSecurityAttr *self, FwupdSecurityAttrResult result)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));

	/* fixup any legacy attributes to the 'modern' value */
	if (g_strcmp0(priv->appstream_id, FWUPD_SECURITY_ATTR_ID_ENCRYPTED_RAM) == 0 &&
	    result == FWUPD_SECURITY_ATTR_RESULT_NOT_ENCRYPTED) {
		result = FWUPD_SECURITY_ATTR_RESULT_NOT_SUPPORTED;
	}

	priv->result = result;
}

/**
 * fwupd_security_attr_get_result:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the optional HSI result.
 *
 * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Since: 1.5.0
 **/
FwupdSecurityAttrResult
fwupd_security_attr_get_result(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->result;
}

/**
 * fwupd_security_attr_set_result_fallback:
 * @self: a #FwupdSecurityAttr
 * @result: a security attribute, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Sets the optional fallback HSI result. The fallback may represent the old state, or a state
 * that may be considered equivalent.
 *
 * Since: 1.7.1
 **/
void
fwupd_security_attr_set_result_fallback(FwupdSecurityAttr *self, FwupdSecurityAttrResult result)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->result_fallback = result;
}

/**
 * fwupd_security_attr_get_result_fallback:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the optional fallback HSI result.
 *
 * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Since: 1.7.1
 **/
FwupdSecurityAttrResult
fwupd_security_attr_get_result_fallback(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->result_fallback;
}

/**
 * fwupd_security_attr_set_result_success:
 * @self: a #FwupdSecurityAttr
 * @result: a security attribute, e.g. %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Sets the desired HSI result.
 *
 * Since: 1.9.3
 **/
void
fwupd_security_attr_set_result_success(FwupdSecurityAttr *self, FwupdSecurityAttrResult result)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	priv->result_success = result;
	fwupd_security_attr_ensure_result(self);
}

/**
 * fwupd_security_attr_get_result_success:
 * @self: a #FwupdSecurityAttr
 *
 * Gets the desired HSI result.
 *
 * Returns: the #FwupdSecurityAttrResult, e.g %FWUPD_SECURITY_ATTR_LEVEL_LOCKED
 *
 * Since: 1.9.3
 **/
FwupdSecurityAttrResult
fwupd_security_attr_get_result_success(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), 0);
	return priv->result_success;
}

static void
fwupd_security_attr_add_variant(FwupdCodec *codec, GVariantBuilder *builder, FwupdCodecFlags flags)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(codec);
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	if (priv->appstream_id != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_APPSTREAM_ID,
				      g_variant_new_string(priv->appstream_id));
	}
	if (priv->created > 0) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_CREATED,
				      g_variant_new_uint64(priv->created));
	}
	if (priv->name != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_NAME,
				      g_variant_new_string(priv->name));
	}
	if (priv->title != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_SUMMARY,
				      g_variant_new_string(priv->title));
	}
	if (priv->description != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_DESCRIPTION,
				      g_variant_new_string(priv->description));
	}
	if (priv->plugin != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_PLUGIN,
				      g_variant_new_string(priv->plugin));
	}
	if (priv->fwupd_version != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_VERSION,
				      g_variant_new_string(priv->fwupd_version));
	}
	if (priv->url != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_URI,
				      g_variant_new_string(priv->url));
	}
	if (priv->obsoletes->len > 0) {
		g_autofree const gchar **strv = g_new0(const gchar *, priv->obsoletes->len + 1);
		for (guint i = 0; i < priv->obsoletes->len; i++)
			strv[i] = (const gchar *)g_ptr_array_index(priv->obsoletes, i);
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_CATEGORIES,
				      g_variant_new_strv(strv, -1));
	}
	if (priv->guids->len > 0) {
		g_autofree const gchar **strv = g_new0(const gchar *, priv->guids->len + 1);
		for (guint i = 0; i < priv->guids->len; i++)
			strv[i] = (const gchar *)g_ptr_array_index(priv->guids, i);
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_GUID,
				      g_variant_new_strv(strv, -1));
	}
	if (priv->flags != 0) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_FLAGS,
				      g_variant_new_uint64(priv->flags));
	}
	if (priv->level > 0) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_HSI_LEVEL,
				      g_variant_new_uint32(priv->level));
	}
	if (priv->result != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_HSI_RESULT,
				      g_variant_new_uint32(priv->result));
	}
	if (priv->result_fallback != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK,
				      g_variant_new_uint32(priv->result_fallback));
	}
	if (priv->result_success != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS,
				      g_variant_new_uint32(priv->result_success));
	}
	if (priv->metadata != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_METADATA,
				      fwupd_variant_from_hash_kv(priv->metadata));
	}
	if (priv->bios_setting_id != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_BIOS_SETTING_ID,
				      g_variant_new_string(priv->bios_setting_id));
	}
	if (priv->bios_setting_target_value != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE,
				      g_variant_new_string(priv->bios_setting_target_value));
	}
	if (priv->bios_setting_current_value != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE,
				      g_variant_new_string(priv->bios_setting_current_value));
	}
	if (priv->kernel_current_value != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE,
				      g_variant_new_string(priv->kernel_current_value));
	}
	if (priv->kernel_target_value != NULL) {
		g_variant_builder_add(builder,
				      "{sv}",
				      FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE,
				      g_variant_new_string(priv->kernel_target_value));
	}
}

/**
 * fwupd_security_attr_get_metadata:
 * @self: a #FwupdSecurityAttr
 * @key: metadata key
 *
 * Gets private metadata from the attribute which may be used in the name.
 *
 * Returns: (nullable): the metadata value, or %NULL if unfound
 *
 * Since: 1.5.0
 **/
const gchar *
fwupd_security_attr_get_metadata(FwupdSecurityAttr *self, const gchar *key)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);
	g_return_val_if_fail(key != NULL, NULL);

	if (priv->metadata == NULL)
		return NULL;
	return g_hash_table_lookup(priv->metadata, key);
}

/**
 * fwupd_security_attr_add_metadata:
 * @self: a #FwupdSecurityAttr
 * @key: metadata key
 * @value: (nullable): metadata value
 *
 * Adds metadata to the attribute which may be used in the name.
 *
 * Since: 1.5.0
 **/
void
fwupd_security_attr_add_metadata(FwupdSecurityAttr *self, const gchar *key, const gchar *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	g_return_if_fail(FWUPD_IS_SECURITY_ATTR(self));
	g_return_if_fail(key != NULL);

	if (priv->metadata == NULL) {
		priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
	}
	g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value));
}

static void
fwupd_security_attr_from_key_value(FwupdSecurityAttr *self, const gchar *key, GVariant *value)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	if (g_strcmp0(key, FWUPD_RESULT_KEY_APPSTREAM_ID) == 0) {
		fwupd_security_attr_set_appstream_id(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_CREATED) == 0) {
		fwupd_security_attr_set_created(self, fwupd_variant_get_uint64(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_NAME) == 0) {
		fwupd_security_attr_set_name(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_SUMMARY) == 0) {
		fwupd_security_attr_set_title(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_DESCRIPTION) == 0) {
		fwupd_security_attr_set_description(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_PLUGIN) == 0) {
		fwupd_security_attr_set_plugin(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_VERSION) == 0) {
		fwupd_security_attr_set_fwupd_version(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_URI) == 0) {
		fwupd_security_attr_set_url(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_FLAGS) == 0) {
		fwupd_security_attr_set_flags(self, fwupd_variant_get_uint64(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_LEVEL) == 0) {
		fwupd_security_attr_set_level(self, fwupd_variant_get_uint32(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT) == 0) {
		fwupd_security_attr_set_result(self, fwupd_variant_get_uint32(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK) == 0) {
		fwupd_security_attr_set_result_fallback(self, fwupd_variant_get_uint32(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS) == 0) {
		fwupd_security_attr_set_result_success(self, fwupd_variant_get_uint32(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_GUID) == 0) {
		g_autofree const gchar **strv = fwupd_variant_get_strv(value);
		for (guint i = 0; strv != NULL && strv[i] != NULL; i++)
			fwupd_security_attr_add_guid(self, strv[i]);
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_METADATA) == 0) {
		g_autoptr(GHashTable) hash = fwupd_variant_to_hash_kv(value);
		if (hash != NULL) {
			if (priv->metadata != NULL)
				g_hash_table_unref(priv->metadata);
			priv->metadata = g_steal_pointer(&hash);
		}
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_ID) == 0) {
		fwupd_security_attr_set_bios_setting_id(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE) == 0) {
		fwupd_security_attr_set_bios_setting_target_value(self,
								  fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE) == 0) {
		fwupd_security_attr_set_bios_setting_current_value(self,
								   fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE) == 0) {
		fwupd_security_attr_set_kernel_current_value(self, fwupd_variant_get_string(value));
		return;
	}
	if (g_strcmp0(key, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE) == 0) {
		fwupd_security_attr_set_kernel_target_value(self, fwupd_variant_get_string(value));
		return;
	}
}

static void
fwupd_security_attr_string_append_tfl(GString *str,
				      guint idt,
				      const gchar *key,
				      FwupdSecurityAttrFlags security_attr_flags)
{
	g_autoptr(GString) tmp = g_string_new("");
	for (guint i = 0; i < 64; i++) {
		if ((security_attr_flags & ((guint64)1 << i)) == 0)
			continue;
		g_string_append_printf(tmp,
				       "%s|",
				       fwupd_security_attr_flag_to_string((guint64)1 << i));
	}
	if (tmp->len == 0) {
		g_string_append(tmp, fwupd_security_attr_flag_to_string(0));
	} else {
		g_string_truncate(tmp, tmp->len - 1);
	}
	fwupd_codec_string_append(str, idt, key, tmp->str);
}

static gboolean
fwupd_security_attr_from_json(FwupdCodec *codec, FwupdJsonObject *json_obj, GError **error)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(codec);
	const gchar *tmp;
	gint64 tmpi = 0;
	g_autoptr(FwupdJsonArray) json_array1 = NULL;
	g_autoptr(FwupdJsonArray) json_array2 = NULL;

	/* this has to exist */
	tmp = fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_APPSTREAM_ID, error);
	if (tmp == NULL)
		return FALSE;
	fwupd_security_attr_set_appstream_id(self, tmp);

	/* all optional */
	fwupd_security_attr_set_name(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_NAME, NULL));
	fwupd_security_attr_set_title(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_SUMMARY, NULL));
	fwupd_security_attr_set_description(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_DESCRIPTION, NULL));
	fwupd_security_attr_set_plugin(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_PLUGIN, NULL));
	fwupd_security_attr_set_fwupd_version(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_VERSION, NULL));
	fwupd_security_attr_set_url(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_URI, NULL));
	if (!fwupd_json_object_get_integer_with_default(json_obj,
							FWUPD_RESULT_KEY_HSI_LEVEL,
							&tmpi,
							0,
							error))
		return FALSE;
	fwupd_security_attr_set_level(self, tmpi);
	if (!fwupd_json_object_get_integer_with_default(json_obj,
							FWUPD_RESULT_KEY_CREATED,
							&tmpi,
							0,
							error))
		return FALSE;
	fwupd_security_attr_set_created(self, tmpi);
	fwupd_security_attr_set_bios_setting_id(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_BIOS_SETTING_ID, NULL));
	fwupd_security_attr_set_bios_setting_target_value(
	    self,
	    fwupd_json_object_get_string(json_obj,
					 FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE,
					 NULL));
	fwupd_security_attr_set_bios_setting_current_value(
	    self,
	    fwupd_json_object_get_string(json_obj,
					 FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE,
					 NULL));
	fwupd_security_attr_set_kernel_current_value(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE, NULL));
	fwupd_security_attr_set_kernel_target_value(
	    self,
	    fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE, NULL));

	/* also optional */
	tmp = fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_HSI_RESULT, NULL);
	if (tmp != NULL)
		fwupd_security_attr_set_result(self, fwupd_security_attr_result_from_string(tmp));

	tmp = fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK, NULL);
	if (tmp != NULL) {
		fwupd_security_attr_set_result_fallback(
		    self,
		    fwupd_security_attr_result_from_string(tmp));
	}
	tmp = fwupd_json_object_get_string(json_obj, FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS, NULL);
	if (tmp != NULL) {
		fwupd_security_attr_set_result_success(self,
						       fwupd_security_attr_result_from_string(tmp));
	}
	json_array1 = fwupd_json_object_get_array(json_obj, FWUPD_RESULT_KEY_FLAGS, NULL);
	if (json_array1 != NULL) {
		for (guint i = 0; i < fwupd_json_array_get_size(json_array1); i++) {
			FwupdSecurityAttrFlags flag = fwupd_security_attr_flag_from_string(
			    fwupd_json_array_get_string(json_array1, i, NULL));
			if (flag != FWUPD_SECURITY_ATTR_FLAG_NONE)
				fwupd_security_attr_add_flag(self, flag);
		}
	}
	json_array2 = fwupd_json_object_get_array(json_obj, FWUPD_RESULT_KEY_GUID, NULL);
	if (json_array2 != NULL) {
		for (guint i = 0; i < fwupd_json_array_get_size(json_array2); i++) {
			fwupd_security_attr_add_guid(
			    self,
			    fwupd_json_array_get_string(json_array2, i, NULL));
		}
	}

	/* success */
	return TRUE;
}

static void
fwupd_security_attr_add_json(FwupdCodec *codec, FwupdJsonObject *json_obj, FwupdCodecFlags flags)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(codec);
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	if (priv->appstream_id != NULL)
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_APPSTREAM_ID,
					     priv->appstream_id);
	if (priv->created > 0)
		fwupd_json_object_add_integer(json_obj, FWUPD_RESULT_KEY_CREATED, priv->created);
	if (priv->level > 0)
		fwupd_json_object_add_integer(json_obj, FWUPD_RESULT_KEY_HSI_LEVEL, priv->level);
	if (priv->result != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN)
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_HSI_RESULT,
					     fwupd_security_attr_result_to_string(priv->result));
	if (priv->result_fallback != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		fwupd_json_object_add_string(
		    json_obj,
		    FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK,
		    fwupd_security_attr_result_to_string(priv->result_fallback));
	}
	if (priv->result_success != FWUPD_SECURITY_ATTR_RESULT_UNKNOWN) {
		fwupd_json_object_add_string(
		    json_obj,
		    FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS,
		    fwupd_security_attr_result_to_string(priv->result_success));
	}
	if (priv->name != NULL)
		fwupd_json_object_add_string(json_obj, FWUPD_RESULT_KEY_NAME, priv->name);
	if (priv->title != NULL)
		fwupd_json_object_add_string(json_obj, FWUPD_RESULT_KEY_SUMMARY, priv->title);
	if (priv->description != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_DESCRIPTION,
					     priv->description);
	}
	if (priv->plugin != NULL)
		fwupd_json_object_add_string(json_obj, FWUPD_RESULT_KEY_PLUGIN, priv->plugin);
	if (priv->fwupd_version != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_VERSION,
					     priv->fwupd_version);
	}
	if (priv->url != NULL)
		fwupd_json_object_add_string(json_obj, FWUPD_RESULT_KEY_URI, priv->url);
	if (priv->bios_setting_target_value != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE,
					     priv->bios_setting_target_value);
	}
	if (priv->bios_setting_current_value != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE,
					     priv->bios_setting_current_value);
	}
	if (priv->bios_setting_id != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_BIOS_SETTING_ID,
					     priv->bios_setting_id);
	}
	if (priv->kernel_current_value != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE,
					     priv->kernel_current_value);
	}
	if (priv->kernel_target_value != NULL) {
		fwupd_json_object_add_string(json_obj,
					     FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE,
					     priv->kernel_target_value);
	}

	if (priv->flags != FWUPD_SECURITY_ATTR_FLAG_NONE) {
		g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new();
		for (guint i = 0; i < 64; i++) {
			const gchar *tmp;
			if ((priv->flags & ((guint64)1 << i)) == 0)
				continue;
			tmp = fwupd_security_attr_flag_to_string((guint64)1 << i);
			if (tmp != NULL)
				fwupd_json_array_add_string(json_arr, tmp);
		}
		fwupd_json_object_add_array(json_obj, FWUPD_RESULT_KEY_FLAGS, json_arr);
	}
	if (priv->guids->len > 0) {
		g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new();
		for (guint i = 0; i < priv->guids->len; i++) {
			const gchar *guid = g_ptr_array_index(priv->guids, i);
			fwupd_json_array_add_string(json_arr, guid);
		}
		fwupd_json_object_add_array(json_obj, FWUPD_RESULT_KEY_GUID, json_arr);
	}
	if (priv->metadata != NULL) {
		g_autoptr(GList) keys =
		    g_list_sort(g_hash_table_get_keys(priv->metadata), (GCompareFunc)g_strcmp0);
		for (GList *l = keys; l != NULL; l = l->next) {
			const gchar *key = l->data;
			const gchar *value = g_hash_table_lookup(priv->metadata, key);
			fwupd_json_object_add_string(json_obj, key, value);
		}
	}
}

static void
fwupd_security_attr_add_string(FwupdCodec *codec, guint idt, GString *str)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(codec);
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_APPSTREAM_ID, priv->appstream_id);
	fwupd_codec_string_append_time(str, idt, FWUPD_RESULT_KEY_CREATED, priv->created);
	fwupd_codec_string_append_int(str, idt, FWUPD_RESULT_KEY_HSI_LEVEL, priv->level);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_HSI_RESULT,
				  fwupd_security_attr_result_to_string(priv->result));
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_HSI_RESULT_FALLBACK,
				  fwupd_security_attr_result_to_string(priv->result_fallback));
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_HSI_RESULT_SUCCESS,
				  fwupd_security_attr_result_to_string(priv->result_success));
	if (priv->flags != FWUPD_SECURITY_ATTR_FLAG_NONE)
		fwupd_security_attr_string_append_tfl(str,
						      idt,
						      FWUPD_RESULT_KEY_FLAGS,
						      priv->flags);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_NAME, priv->name);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_SUMMARY, priv->title);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_DESCRIPTION, priv->description);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_PLUGIN, priv->plugin);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_VERSION, priv->fwupd_version);
	fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_URI, priv->url);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_BIOS_SETTING_ID,
				  priv->bios_setting_id);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_BIOS_SETTING_TARGET_VALUE,
				  priv->bios_setting_target_value);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_BIOS_SETTING_CURRENT_VALUE,
				  priv->bios_setting_current_value);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_KERNEL_CURRENT_VALUE,
				  priv->kernel_current_value);
	fwupd_codec_string_append(str,
				  idt,
				  FWUPD_RESULT_KEY_KERNEL_TARGET_VALUE,
				  priv->kernel_target_value);

	for (guint i = 0; i < priv->obsoletes->len; i++) {
		const gchar *appstream_id = g_ptr_array_index(priv->obsoletes, i);
		fwupd_codec_string_append(str, idt, "Obsolete", appstream_id);
	}
	for (guint i = 0; i < priv->guids->len; i++) {
		const gchar *guid = g_ptr_array_index(priv->guids, i);
		fwupd_codec_string_append(str, idt, FWUPD_RESULT_KEY_GUID, guid);
	}
	if (priv->metadata != NULL) {
		g_autoptr(GList) keys =
		    g_list_sort(g_hash_table_get_keys(priv->metadata), (GCompareFunc)g_strcmp0);
		for (GList *l = keys; l != NULL; l = l->next) {
			const gchar *key = l->data;
			const gchar *value = g_hash_table_lookup(priv->metadata, key);
			fwupd_codec_string_append(str, idt, key, value);
		}
	}
}

static void
fwupd_security_attr_class_init(FwupdSecurityAttrClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS(klass);
	object_class->finalize = fwupd_security_attr_finalize;
}

static void
fwupd_security_attr_init(FwupdSecurityAttr *self)
{
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);
	priv->level = FWUPD_SECURITY_ATTR_LEVEL_NONE;
	priv->obsoletes = g_ptr_array_new_with_free_func(g_free);
	priv->guids = g_ptr_array_new_with_free_func(g_free);
	priv->created = (guint64)g_get_real_time() / G_USEC_PER_SEC;
}

static void
fwupd_security_attr_finalize(GObject *object)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(object);
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	if (priv->metadata != NULL)
		g_hash_table_unref(priv->metadata);
	g_free(priv->bios_setting_id);
	g_free(priv->bios_setting_target_value);
	g_free(priv->bios_setting_current_value);
	g_free(priv->kernel_current_value);
	g_free(priv->kernel_target_value);
	g_free(priv->appstream_id);
	g_free(priv->name);
	g_free(priv->title);
	g_free(priv->description);
	g_free(priv->plugin);
	g_free(priv->fwupd_version);
	g_free(priv->url);
	g_ptr_array_unref(priv->obsoletes);
	g_ptr_array_unref(priv->guids);

	G_OBJECT_CLASS(fwupd_security_attr_parent_class)->finalize(object);
}

static void
fwupd_security_attr_from_variant_iter(FwupdCodec *codec, GVariantIter *iter)
{
	FwupdSecurityAttr *self = FWUPD_SECURITY_ATTR(codec);
	GVariant *value;
	const gchar *key;
	while (g_variant_iter_next(iter, "{&sv}", &key, &value)) {
		fwupd_security_attr_from_key_value(self, key, value);
		g_variant_unref(value);
	}
}

static void
fwupd_security_attr_codec_iface_init(FwupdCodecInterface *iface)
{
	iface->add_string = fwupd_security_attr_add_string;
	iface->add_json = fwupd_security_attr_add_json;
	iface->from_json = fwupd_security_attr_from_json;
	iface->add_variant = fwupd_security_attr_add_variant;
	iface->from_variant_iter = fwupd_security_attr_from_variant_iter;
}

/**
 * fwupd_security_attr_copy:
 * @self: (nullable): a #FwupdSecurityAttr
 *
 * Makes a full (deep) copy of a security attribute.
 *
 * Returns: (transfer full): a new #FwupdSecurityAttr
 *
 * Since: 1.7.1
 **/
FwupdSecurityAttr *
fwupd_security_attr_copy(FwupdSecurityAttr *self)
{
	g_autoptr(FwupdSecurityAttr) new = g_object_new(FWUPD_TYPE_SECURITY_ATTR, NULL);
	FwupdSecurityAttrPrivate *priv = GET_PRIVATE(self);

	g_return_val_if_fail(FWUPD_IS_SECURITY_ATTR(self), NULL);

	fwupd_security_attr_set_appstream_id(new, priv->appstream_id);
	fwupd_security_attr_set_name(new, priv->name);
	fwupd_security_attr_set_title(new, priv->title);
	fwupd_security_attr_set_description(new, priv->description);
	fwupd_security_attr_set_plugin(new, priv->plugin);
	fwupd_security_attr_set_fwupd_version(new, priv->fwupd_version);
	fwupd_security_attr_set_url(new, priv->url);
	fwupd_security_attr_set_level(new, priv->level);
	fwupd_security_attr_set_flags(new, priv->flags);
	fwupd_security_attr_set_result(new, priv->result);
	fwupd_security_attr_set_result_fallback(new, priv->result_fallback);
	fwupd_security_attr_set_result_success(new, priv->result_success);
	fwupd_security_attr_set_created(new, priv->created);
	fwupd_security_attr_set_bios_setting_id(new, priv->bios_setting_id);
	fwupd_security_attr_set_bios_setting_target_value(new, priv->bios_setting_target_value);
	fwupd_security_attr_set_bios_setting_current_value(new, priv->bios_setting_current_value);
	fwupd_security_attr_set_kernel_current_value(new, priv->kernel_current_value);
	fwupd_security_attr_set_kernel_target_value(new, priv->kernel_target_value);

	for (guint i = 0; i < priv->guids->len; i++) {
		const gchar *guid = g_ptr_array_index(priv->guids, i);
		fwupd_security_attr_add_guid(new, guid);
	}
	for (guint i = 0; i < priv->obsoletes->len; i++) {
		const gchar *obsolete = g_ptr_array_index(priv->obsoletes, i);
		fwupd_security_attr_add_obsolete(new, obsolete);
	}
	if (priv->metadata != NULL) {
		GHashTableIter iter;
		gpointer key;
		gpointer value;
		g_hash_table_iter_init(&iter, priv->metadata);
		while (g_hash_table_iter_next(&iter, &key, &value)) {
			fwupd_security_attr_add_metadata(new,
							 (const gchar *)key,
							 (const gchar *)value);
		}
	}
	return g_steal_pointer(&new);
}

/**
 * fwupd_security_attr_new:
 * @appstream_id: (nullable): the AppStream component ID, e.g. `com.intel.BiosGuard`
 *
 * Creates a new security attribute.
 *
 * Plugins should not use this method, and should instead use `fu_plugin_security_attr_new()` or
 * `fu_security_attr_new()`.
 *
 * Returns: a new #FwupdSecurityAttr
 *
 * Since: 1.5.0
 **/
FwupdSecurityAttr *
fwupd_security_attr_new(const gchar *appstream_id)
{
	FwupdSecurityAttr *self;
	self = g_object_new(FWUPD_TYPE_SECURITY_ATTR, NULL);
	if (appstream_id != NULL)
		fwupd_security_attr_set_appstream_id(self, appstream_id);
	return FWUPD_SECURITY_ATTR(self);
}
