/*
 * Copyright (C) 2017-2018 Richard Hughes <richard@hughsie.com>
 *
 * SPDX-License-Identifier: LGPL-2.1+
 */

#include "config.h"

#include <json-glib/json-glib.h>
#include <libsoup/soup.h>
#include <string.h>

#include "fwupd-error.h"
#include "fwupd-enums.h"

#include "fu-device.h"

#include "fu-redfish-client.h"
#include "fu-redfish-common.h"

struct _FuRedfishClient
{
	GObject			 parent_instance;
	SoupSession		*session;
	gchar			*hostname;
	guint			 port;
	gchar			*username;
	gchar			*password;
	gchar			*update_uri_path;
	gchar			*push_uri_path;
	gboolean		 auth_created;
	gboolean		 use_https;
	gboolean		 cacheck;
	GPtrArray		*devices;
};

G_DEFINE_TYPE (FuRedfishClient, fu_redfish_client, G_TYPE_OBJECT)

static void
fu_redfish_client_set_auth (FuRedfishClient *self, SoupURI *uri,
			    SoupMessage *msg)
{
	if ((self->username != NULL && self->password != NULL) &&
	    self->auth_created == FALSE) {
		/*
		 * Some redfish implementations miss WWW-Authenticate
		 * header for a 401 response, and SoupAuthManager couldn't
		 * generate SoupAuth accordingly. Since DSP0266 makes
		 * Basic Authorization a requirement for redfish, it shall be
		 * safe to use Basic Auth for all redfish implementations.
		 */
		SoupAuthManager *manager = SOUP_AUTH_MANAGER (soup_session_get_feature (self->session, SOUP_TYPE_AUTH_MANAGER));
		g_autoptr(SoupAuth) auth = soup_auth_new (SOUP_TYPE_AUTH_BASIC,
							  msg, "Basic");
		soup_auth_authenticate (auth, self->username, self->password);
		soup_auth_manager_use_auth (manager, uri, auth);
		self->auth_created = TRUE;
	}
}

static GBytes *
fu_redfish_client_fetch_data (FuRedfishClient *self, const gchar *uri_path, GError **error)
{
	guint status_code;
	g_autoptr(SoupMessage) msg = NULL;
	g_autoptr(SoupURI) uri = NULL;

	/* create URI */
	uri = soup_uri_new (NULL);
	soup_uri_set_scheme (uri, self->use_https ? "https" : "http");
	soup_uri_set_path (uri, uri_path);
	soup_uri_set_host (uri, self->hostname);
	soup_uri_set_port (uri, self->port);
	msg = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
	if (msg == NULL) {
		g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE);
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "failed to create message for URI %s", tmp);
		return NULL;
	}
	fu_redfish_client_set_auth (self, uri, msg);
	status_code = soup_session_send_message (self->session, msg);
	if (status_code != SOUP_STATUS_OK) {
		g_autofree gchar *tmp = soup_uri_to_string (uri, FALSE);
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "failed to download %s: %s",
			     tmp, soup_status_get_phrase (status_code));
		return NULL;
	}
	return g_bytes_new (msg->response_body->data, msg->response_body->length);
}

static gboolean
fu_redfish_client_coldplug_member (FuRedfishClient *self,
				   JsonObject *member,
				   GError **error)
{
	g_autoptr(FuDevice) dev = NULL;
	const gchar *guid = NULL;
	g_autofree gchar *id = NULL;

	if (json_object_has_member (member, "SoftwareId")) {
		guid = json_object_get_string_member (member, "SoftwareId");
	} else if (json_object_has_member (member, "Oem")) {
		JsonObject *oem = json_object_get_object_member (member, "Oem");
		if (oem != NULL && json_object_has_member (oem, "Hpe")) {
			JsonObject *hpe = json_object_get_object_member (oem, "Hpe");
			if (hpe != NULL && json_object_has_member (hpe, "DeviceClass"))
				guid = json_object_get_string_member (hpe, "DeviceClass");
		}
	}

	/* skip the devices without guid */
	if (guid == NULL)
		return TRUE;

	dev = fu_device_new ();

	id = g_strdup_printf ("Redfish-Inventory-%s",
			      json_object_get_string_member (member, "Id"));
	fu_device_set_id (dev, id);

	fu_device_add_guid (dev, guid);
	if (json_object_has_member (member, "Name"))
		fu_device_set_name (dev, json_object_get_string_member (member, "Name"));
	fu_device_set_summary (dev, "Redfish device");
	if (json_object_has_member (member, "Version")) {
		fu_device_set_version (dev, json_object_get_string_member (member, "Version"),
				       FWUPD_VERSION_FORMAT_UNKNOWN);
	}
	if (json_object_has_member (member, "LowestSupportedVersion"))
		fu_device_set_version_lowest (dev, json_object_get_string_member (member, "LowestSupportedVersion"));
	if (json_object_has_member (member, "Description"))
		fu_device_set_description (dev, json_object_get_string_member (member, "Description"));
	if (json_object_has_member (member, "Updateable")) {
		if (json_object_get_boolean_member (member, "Updateable"))
			fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
	} else {
		/* assume the device is updatable */
		fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE);
	}

	/* success */
	g_ptr_array_add (self->devices, g_steal_pointer (&dev));
	return TRUE;
}

static gboolean
fu_redfish_client_coldplug_collection (FuRedfishClient *self,
				       JsonObject *collection,
				       GError **error)
{
	JsonArray *members;
	JsonNode *node_root;
	JsonObject *member;

	members = json_object_get_array_member (collection, "Members");
	for (guint i = 0; i < json_array_get_length (members); i++) {
		g_autoptr(JsonParser) parser = json_parser_new ();
		g_autoptr(GBytes) blob = NULL;
		JsonObject *member_id;
		const gchar *member_uri;

		member_id = json_array_get_object_element (members, i);
		member_uri = json_object_get_string_member (member_id, "@odata.id");
		if (member_uri == NULL) {
			g_set_error_literal (error,
					     FWUPD_ERROR,
					     FWUPD_ERROR_NOT_FOUND,
					     "no @odata.id string");
			return FALSE;
		}

		/* try to connect */
		blob = fu_redfish_client_fetch_data (self, member_uri, error);
		if (blob == NULL)
			return FALSE;

		/* get the member object */
		if (!json_parser_load_from_data (parser,
						 g_bytes_get_data (blob, NULL),
						 (gssize) g_bytes_get_size (blob),
						 error)) {
			g_prefix_error (error, "failed to parse node: ");
			return FALSE;
		}
		node_root = json_parser_get_root (parser);
		if (node_root == NULL) {
			g_set_error_literal (error,
					     FWUPD_ERROR,
					     FWUPD_ERROR_INVALID_FILE,
					     "no root node");
			return FALSE;
		}
		member = json_node_get_object (node_root);
		if (member == NULL) {
			g_set_error_literal (error,
					     FWUPD_ERROR,
					     FWUPD_ERROR_INVALID_FILE,
					     "no member object");
			return FALSE;
		}

		/* Create the device for the member */
		if (!fu_redfish_client_coldplug_member (self, member, error))
			return FALSE;
	}
	return TRUE;
}

static gboolean
fu_redfish_client_coldplug_inventory (FuRedfishClient *self,
				      JsonObject *inventory,
				      GError **error)
{
	g_autoptr(JsonParser) parser = json_parser_new ();
	g_autoptr(GBytes) blob = NULL;
	JsonNode *node_root;
	JsonObject *collection;
	const gchar *collection_uri;

	if (inventory == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_FOUND,
				     "no inventory object");
		return FALSE;
	}

	collection_uri = json_object_get_string_member (inventory, "@odata.id");
	if (collection_uri == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_FOUND,
				     "no @odata.id string");
		return FALSE;
	}

	/* try to connect */
	blob = fu_redfish_client_fetch_data (self, collection_uri, error);
	if (blob == NULL)
		return FALSE;

	/* get the inventory object */
	if (!json_parser_load_from_data (parser,
					 g_bytes_get_data (blob, NULL),
					 (gssize) g_bytes_get_size (blob),
					 error)) {
		g_prefix_error (error, "failed to parse node: ");
		return FALSE;
	}
	node_root = json_parser_get_root (parser);
	if (node_root == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no root node");
		return FALSE;
	}
	collection = json_node_get_object (node_root);
	if (collection == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no collection object");
		return FALSE;
	}

	return fu_redfish_client_coldplug_collection (self, collection, error);
}

gboolean
fu_redfish_client_coldplug (FuRedfishClient *self, GError **error)
{
	JsonNode *node_root;
	JsonObject *obj_root = NULL;
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(JsonParser) parser = json_parser_new ();

	/* nothing set */
	if (self->update_uri_path == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INTERNAL,
				     "no update_uri_path");
		return FALSE;
	}

	/* try to connect */
	blob = fu_redfish_client_fetch_data (self, self->update_uri_path, error);
	if (blob == NULL)
		return FALSE;

	/* get the update service */
	if (!json_parser_load_from_data (parser,
					 g_bytes_get_data (blob, NULL),
					 (gssize) g_bytes_get_size (blob),
					 error)) {
		g_prefix_error (error, "failed to parse node: ");
		return FALSE;
	}
	node_root = json_parser_get_root (parser);
	if (node_root == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no root node");
		return FALSE;
	}
	obj_root = json_node_get_object (node_root);
	if (obj_root == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no root object");
		return FALSE;
	}
	if (!json_object_get_boolean_member (obj_root, "ServiceEnabled")) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "service is not enabled");
		return FALSE;
	}
	if (!json_object_has_member (obj_root, "HttpPushUri")) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "HttpPushUri is not available");
		return FALSE;
	}
	self->push_uri_path = g_strdup (json_object_get_string_member (obj_root, "HttpPushUri"));
	if (self->push_uri_path == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "HttpPushUri is invalid");
		return FALSE;
	}
	if (json_object_has_member (obj_root, "FirmwareInventory")) {
		JsonObject *tmp = json_object_get_object_member (obj_root, "FirmwareInventory");
		return fu_redfish_client_coldplug_inventory (self, tmp, error);
	}
	if (json_object_has_member (obj_root, "SoftwareInventory")) {
		JsonObject *tmp = json_object_get_object_member (obj_root, "SoftwareInventory");
		return fu_redfish_client_coldplug_inventory (self, tmp, error);
	}
	return TRUE;
}

static gboolean
fu_redfish_client_set_uefi_credentials (FuRedfishClient *self, GError **error)
{
	guint32 indications_le;
	g_autofree gchar *userpass_safe = NULL;
	g_auto(GStrv) split = NULL;
	g_autoptr(GBytes) indications = NULL;
	g_autoptr(GBytes) userpass = NULL;

	/* get the uint32 specifying if there are EFI variables set */
	indications = fu_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID,
						     REDFISH_EFI_INFORMATION_INDICATIONS,
						     error);
	if (indications == NULL)
		return FALSE;
	if (g_bytes_get_size (indications) != 4) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "invalid value for %s, got %" G_GSIZE_FORMAT " bytes",
			     REDFISH_EFI_INFORMATION_INDICATIONS,
			     g_bytes_get_size (indications));
		return FALSE;
	}
	memcpy (&indications_le, g_bytes_get_data (indications, NULL), 4);
	if ((indications_le & REDFISH_EFI_INDICATIONS_OS_CREDENTIALS) == 0) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no indications for OS credentials");
		return FALSE;
	}

	/* read the correct EFI var for runtime */
	userpass = fu_redfish_common_get_evivar_raw (REDFISH_EFI_INFORMATION_GUID,
						  REDFISH_EFI_INFORMATION_OS_CREDENTIALS,
						  error);
	if (userpass == NULL)
		return FALSE;

	/* it might not be NUL terminated */
	userpass_safe = g_strndup (g_bytes_get_data (userpass, NULL),
				   g_bytes_get_size (userpass));
	split = g_strsplit (userpass_safe, ":", -1);
	if (g_strv_length (split) != 2) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "invalid format for username:password, got '%s'",
			     userpass_safe);
		return FALSE;
	}
	fu_redfish_client_set_username (self, split[0]);
	fu_redfish_client_set_password (self, split[1]);
	return TRUE;
}

static void
fu_redfish_client_parse_interface_data (const guint8 *buf, guint8 sz)
{
	switch (buf[0]) {
	case REDFISH_INTERFACE_TYPE_USB_NEWORK:
		g_debug ("USB Network Interface");
		/*
		 * uint16	idVendor(2-bytes)
		 * uint16	idProduct(2-bytes)
		 * uint8	SerialNumberLen:
		 * uint8	DescriptorType:
		 * uint8*	SerialNumber:
		 */
		break;
	case REDFISH_INTERFACE_TYPE_PCI_NEWORK:
		g_debug ("PCI Network Interface");
		/*
		 * uint16	VendorID
		 * uint16	DeviceID
		 * uint16	Subsystem_Vendor_ID
		 * uint16	Subsystem_ID
		 */
		break;
	default:
		break;
	}
}

typedef struct __attribute__((packed)) {
	guint8		 service_uuid[16];
	guint8		 host_ip_assignment_type;
	guint8		 host_ip_address_format;
	guint8		 host_ip_address[16];
	guint8		 host_ip_mask[16];
	guint8		 service_ip_assignment_type;
	guint8		 service_ip_address_format;
	guint8		 service_ip_address[16];
	guint8		 service_ip_mask[16];
	guint16		 service_ip_port;
	guint32		 service_ip_vlan_id;
	guint8		 service_hostname_len;
	/*		 service_hostname; */
} RedfishProtocolDataOverIp;

static gboolean
fu_redfish_client_parse_protocol_data (FuRedfishClient *self,
				       const guint8 *buf,
				       guint8 sz,
				       GError **error)
{
	RedfishProtocolDataOverIp *pr;
	if (sz < sizeof(RedfishProtocolDataOverIp)) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "protocol data too small");
		return FALSE;
	}
	pr = (RedfishProtocolDataOverIp *) buf;

	/* parse the hostname and port */
	if (pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_STATIC ||
	    pr->service_ip_assignment_type == REDFISH_IP_ASSIGNMENT_TYPE_AUTO_CONFIG) {
		if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) {
			g_autofree gchar *tmp = NULL;
			tmp = fu_redfish_common_buffer_to_ipv4 (pr->service_ip_address);
			fu_redfish_client_set_hostname (self, tmp);
		} else if (pr->service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) {
			g_autofree gchar *tmp = NULL;
			tmp = fu_redfish_common_buffer_to_ipv6 (pr->service_ip_address);
			fu_redfish_client_set_hostname (self, tmp);
		} else {
			g_set_error_literal (error,
					     FWUPD_ERROR,
					     FWUPD_ERROR_INVALID_FILE,
					     "address format is invalid");
			return FALSE;
		}
		fu_redfish_client_set_port (self, pr->service_ip_port);
	} else {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "DHCP address formats not supported (%0x2)",
			     pr->service_ip_assignment_type);
		return FALSE;
	}

	return TRUE;
}

static gboolean
fu_redfish_client_set_smbios_interfaces (FuRedfishClient *self,
					 GBytes *smbios_table,
					 GError **error)
{
	const guint8 *buf;
	gsize sz = 0;

	/* check size */
	buf = g_bytes_get_data (smbios_table, &sz);
	if (sz < 0x09) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "SMBIOS entry too small: %" G_GSIZE_FORMAT,
			     sz);
		return FALSE;
	}

	/* check interface type */
	if (buf[0x04] != REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "only Network Host Interface supported");
		return FALSE;
	}

	/* check length */
	if (buf[0x05] > sz - 0x08) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "interface specific data too large %u > %" G_GSIZE_FORMAT,
			     buf[0x05], sz - 0x08);
		return FALSE;
	}

	/* parse data, for not just for debugging */
	if (buf[0x05] > 0)
		fu_redfish_client_parse_interface_data (&buf[0x06], buf[0x05]);

	/* parse protocol records */
	for (guint8 i = 0x07 + buf[0x05]; i < sz - 1; i++) {
		guint8 protocol_id = buf[i];
		guint8 protocol_sz = buf[i+1];
		if (protocol_sz > sz - buf[0x05] + 0x07) {
			g_set_error_literal (error,
					     FWUPD_ERROR,
					     FWUPD_ERROR_INVALID_FILE,
					     "protocol length too large");
			return FALSE;
		}
		if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) {
			if (!fu_redfish_client_parse_protocol_data (self,
								    &buf[i+2],
								    protocol_sz,
								    error))
				return FALSE;
		} else {
			g_debug ("ignoring unsupported protocol ID %02x",
				 protocol_id);
		}
		i += protocol_sz - 1;
	}

	return TRUE;
}

gboolean
fu_redfish_client_update (FuRedfishClient *self, FuDevice *device, GBytes *blob_fw,
			  GError **error)
{
	FwupdRelease *release;
	g_autofree gchar *filename = NULL;

	guint status_code;
	g_autoptr(SoupMessage) msg = NULL;
	g_autoptr(SoupURI) uri = NULL;
	g_autoptr(SoupMultipart) multipart = NULL;
	g_autoptr(SoupBuffer) buffer = NULL;
	g_autofree gchar *uri_str = NULL;

	/* Get the update version */
	release = fwupd_device_get_release_default (FWUPD_DEVICE (device));
	if (release != NULL) {
		filename = g_strdup_printf ("%s-%s.bin",
					    fu_device_get_name (device),
					    fwupd_release_get_version (release));
	} else {
		filename = g_strdup_printf ("%s.bin",
					    fu_device_get_name (device));
	}

	/* create URI */
	uri = soup_uri_new (NULL);
	soup_uri_set_scheme (uri, self->use_https ? "https" : "http");
	soup_uri_set_path (uri, self->push_uri_path);
	soup_uri_set_host (uri, self->hostname);
	soup_uri_set_port (uri, self->port);
	uri_str = soup_uri_to_string (uri, FALSE);

	/* Create the multipart request */
	multipart = soup_multipart_new (SOUP_FORM_MIME_TYPE_MULTIPART);
	buffer = soup_buffer_new (SOUP_MEMORY_COPY,
				  g_bytes_get_data (blob_fw, NULL),
				  g_bytes_get_size (blob_fw));
	soup_multipart_append_form_file (multipart, filename, filename,
					 "application/octet-stream",
					 buffer);
	msg = soup_form_request_new_from_multipart (uri_str, multipart);
	if (msg == NULL) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "failed to create message for URI %s", uri_str);
		return FALSE;
	}
	fu_redfish_client_set_auth (self, uri, msg);
	status_code = soup_session_send_message (self->session, msg);
	if (status_code != SOUP_STATUS_OK) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "failed to upload %s to %s: %s",
			     filename, uri_str,
			     soup_status_get_phrase (status_code));
		return FALSE;
	}

	return TRUE;
}

gboolean
fu_redfish_client_setup (FuRedfishClient *self, GBytes *smbios_table, GError **error)
{
	JsonNode *node_root;
	JsonObject *obj_root = NULL;
	JsonObject *obj_update_service = NULL;
	const gchar *data_id;
	const gchar *version = NULL;
	g_autofree gchar *user_agent = NULL;
	g_autoptr(GBytes) blob = NULL;
	g_autoptr(JsonParser) parser = json_parser_new ();

	/* sanity check */
	if (self->port == 0) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INTERNAL,
				     "no port specified");
		return FALSE;
	}
	if (self->port > 0xffff) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INTERNAL,
			     "invalid port specified: 0x%x",
			     self->port);
		return FALSE;
	}

	/* create the soup session */
	user_agent = g_strdup_printf ("%s/%s", PACKAGE_NAME, PACKAGE_VERSION);
	self->session = soup_session_new_with_options (SOUP_SESSION_USER_AGENT, user_agent,
						       SOUP_SESSION_TIMEOUT, 60,
						       NULL);
	if (self->session == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INTERNAL,
				     "failed to setup networking");
		return FALSE;
	}

	if (self->cacheck == FALSE) {
		g_object_set (G_OBJECT (self->session),
			      SOUP_SESSION_SSL_STRICT, FALSE,
			      NULL);
	}

	/* this is optional */
	if (smbios_table != NULL) {
		g_autoptr(GError) error_smbios = NULL;
		g_autoptr(GError) error_uefi = NULL;
		if (!fu_redfish_client_set_smbios_interfaces (self,
							   smbios_table,
							   &error_smbios)) {
			g_debug ("failed to get connection URI automatically: %s",
				 error_smbios->message);
		}
		if (!fu_redfish_client_set_uefi_credentials (self, &error_uefi)) {
			g_debug ("failed to get username and password automatically: %s",
				 error_uefi->message);
		}
	}
	if (self->hostname != NULL)
		g_debug ("Hostname: %s", self->hostname);
	if (self->port != 0)
		g_debug ("Port:     %u", self->port);
	if (self->username != NULL)
		g_debug ("Username: %s", self->username);
	if (self->password != NULL)
		g_debug ("Password: %s", self->password);

	/* try to connect */
	blob = fu_redfish_client_fetch_data (self, "/redfish/v1/", error);
	if (blob == NULL)
		return FALSE;

	/* get the update service */
	if (!json_parser_load_from_data (parser,
					 g_bytes_get_data (blob, NULL),
					 (gssize) g_bytes_get_size (blob),
					 error)) {
		g_prefix_error (error, "failed to parse node: ");
		return FALSE;
	}
	node_root = json_parser_get_root (parser);
	if (node_root == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no root node");
		return FALSE;
	}
	obj_root = json_node_get_object (node_root);
	if (obj_root == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no root object");
		return FALSE;
	}
	if (json_object_has_member (obj_root, "ServiceVersion")) {
		version = json_object_get_string_member (obj_root,
							 "ServiceVersion");
	} else if (json_object_has_member (obj_root, "RedfishVersion")) {
		version = json_object_get_string_member (obj_root,
							 "RedfishVersion");
	}
	g_debug ("Version:  %s", version);
	g_debug ("UUID:     %s",
		 json_object_get_string_member (obj_root, "UUID"));

	if (json_object_has_member (obj_root, "UpdateService"))
		obj_update_service = json_object_get_object_member (obj_root, "UpdateService");
	if (obj_update_service == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "no UpdateService object");
		return FALSE;
	}
	data_id = json_object_get_string_member (obj_update_service, "@odata.id");
	if (data_id == NULL) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_INVALID_FILE,
				     "no @odata.id string");
		return FALSE;
	}
	self->update_uri_path = g_strdup (data_id);
	return TRUE;
}

GPtrArray *
fu_redfish_client_get_devices (FuRedfishClient *self)
{
	return self->devices;
}

void
fu_redfish_client_set_hostname (FuRedfishClient *self, const gchar *hostname)
{
	g_free (self->hostname);
	self->hostname = g_strdup (hostname);
}

void
fu_redfish_client_set_port (FuRedfishClient *self, guint port)
{
	self->port = port;
}

void
fu_redfish_client_set_https (FuRedfishClient *self, gboolean use_https)
{
	self->use_https = use_https;
}

void
fu_redfish_client_set_cacheck (FuRedfishClient *self, gboolean cacheck)
{
	self->cacheck = cacheck;
}

void
fu_redfish_client_set_username (FuRedfishClient *self, const gchar *username)
{
	g_free (self->username);
	self->username = g_strdup (username);
}

void
fu_redfish_client_set_password (FuRedfishClient *self, const gchar *password)
{
	g_free (self->password);
	self->password = g_strdup (password);
}

static void
fu_redfish_client_finalize (GObject *object)
{
	FuRedfishClient *self = FU_REDFISH_CLIENT (object);
	if (self->session != NULL)
		g_object_unref (self->session);
	g_free (self->update_uri_path);
	g_free (self->push_uri_path);
	g_free (self->hostname);
	g_free (self->username);
	g_free (self->password);
	g_ptr_array_unref (self->devices);
	G_OBJECT_CLASS (fu_redfish_client_parent_class)->finalize (object);
}

static void
fu_redfish_client_class_init (FuRedfishClientClass *klass)
{
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
	object_class->finalize = fu_redfish_client_finalize;
}

static void
fu_redfish_client_init (FuRedfishClient *self)
{
	self->devices = g_ptr_array_new_with_free_func ((GDestroyNotify) g_object_unref);
}

FuRedfishClient *
fu_redfish_client_new (void)
{
	FuRedfishClient *self;
	self = g_object_new (REDFISH_TYPE_CLIENT, NULL);
	return FU_REDFISH_CLIENT (self);
}

/* vim: set noexpandtab: */
