/* * Copyright (C) 2017 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ */ #include "config.h" #include #include "fu-redfish-common.h" #include "fu-redfish-smbios.h" struct _FuRedfishSmbios { FuFirmwareClass parent_instance; guint16 port; gchar *hostname; gchar *mac_addr; gchar *ip_addr; guint16 vid; guint16 pid; }; G_DEFINE_TYPE (FuRedfishSmbios, fu_redfish_smbios, FU_TYPE_FIRMWARE) 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; /* optional service_hostname goes here */ } FuRedfishProtocolOverIp; guint16 fu_redfish_smbios_get_port (FuRedfishSmbios *self) { return self->port; } guint16 fu_redfish_smbios_get_vid (FuRedfishSmbios *self) { return self->vid; } guint16 fu_redfish_smbios_get_pid (FuRedfishSmbios *self) { return self->pid; } const gchar * fu_redfish_smbios_get_hostname (FuRedfishSmbios *self) { return self->hostname; } const gchar * fu_redfish_smbios_get_mac_addr (FuRedfishSmbios *self) { return self->mac_addr; } const gchar * fu_redfish_smbios_get_ip_addr (FuRedfishSmbios *self) { return self->ip_addr; } static void fu_redfish_smbios_set_hostname (FuRedfishSmbios *self, const gchar *hostname) { g_free (self->hostname); self->hostname = g_strdup (hostname); } static void fu_redfish_smbios_set_mac_addr (FuRedfishSmbios *self, const gchar *mac_addr) { g_free (self->mac_addr); self->mac_addr = g_strdup (mac_addr); } static void fu_redfish_smbios_set_ip_addr (FuRedfishSmbios *self, const gchar *ip_addr) { g_free (self->ip_addr); self->ip_addr = g_strdup (ip_addr); } static void fu_redfish_smbios_set_port (FuRedfishSmbios *self, guint16 port) { self->port = port; } static void fu_redfish_smbios_export (FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS (firmware); fu_xmlb_builder_insert_kx (bn, "port", self->port); fu_xmlb_builder_insert_kv (bn, "hostname", self->hostname); fu_xmlb_builder_insert_kv (bn, "mac_addr", self->mac_addr); fu_xmlb_builder_insert_kv (bn, "ip_addr", self->ip_addr); fu_xmlb_builder_insert_kx (bn, "vid", self->vid); fu_xmlb_builder_insert_kx (bn, "pid", self->pid); } static gboolean fu_redfish_smbios_build (FuFirmware *firmware, XbNode *n, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS (firmware); const gchar *tmp; guint64 tmpu; /* optional properties */ tmpu = xb_node_query_text_as_uint (n, "port", NULL); if (tmpu != G_MAXUINT64) fu_redfish_smbios_set_port (self, (guint16) tmpu); tmpu = xb_node_query_text_as_uint (n, "vid", NULL); if (tmpu != G_MAXUINT64) self->vid = (guint16) tmpu; tmpu = xb_node_query_text_as_uint (n, "pid", NULL); if (tmpu != G_MAXUINT64) self->pid = (guint16) tmpu; tmp = xb_node_query_text (n, "hostname", NULL); if (tmp != NULL) fu_redfish_smbios_set_hostname (self, tmp); tmp = xb_node_query_text (n, "mac_addr", NULL); if (tmp != NULL) fu_redfish_smbios_set_mac_addr (self, tmp); tmp = xb_node_query_text (n, "ip_addr", NULL); if (tmp != NULL) fu_redfish_smbios_set_ip_addr (self, tmp); /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse_interface_data (FuRedfishSmbios *self, GBytes *fw, gsize offset, GError **error) { gsize bufsz = 0; gsize offset_mac_addr = G_MAXSIZE; gsize offset_vid_pid = G_MAXSIZE; guint8 interface_type = 0x0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); /* parse the data depending on the interface type */ if (!fu_common_read_uint8_safe (buf, bufsz, offset, &interface_type, error)) return FALSE; offset++; switch (interface_type) { case REDFISH_INTERFACE_TYPE_USB_NETWORK: case REDFISH_INTERFACE_TYPE_PCI_NETWORK: offset_vid_pid = 0x00; break; case REDFISH_INTERFACE_TYPE_USB_NETWORK_V2: offset_vid_pid = 0x01; offset_mac_addr = 0x06; break; case REDFISH_INTERFACE_TYPE_PCI_NETWORK_V2: offset_vid_pid = 0x01; offset_mac_addr = 0x09; break; default: g_debug ("unknown Network Interface"); break; } /* MAC address */ if (offset_mac_addr != G_MAXSIZE) { guint8 mac_addr[6] = { 0x0 }; g_autofree gchar *mac_addr_str = NULL; if (!fu_memcpy_safe (mac_addr, sizeof(mac_addr), 0x0, /* dst */ buf, bufsz, offset + offset_mac_addr, /* src */ sizeof(mac_addr), error)) return FALSE; mac_addr_str = fu_redfish_common_buffer_to_mac (mac_addr); fu_redfish_smbios_set_mac_addr (self, mac_addr_str); } /* VID:PID */ if (offset_vid_pid != G_MAXSIZE) { if (!fu_common_read_uint16_safe (buf, bufsz, offset + offset_vid_pid, &self->vid, G_LITTLE_ENDIAN, error)) return FALSE; if (!fu_common_read_uint16_safe (buf, bufsz, offset + offset_vid_pid + 0x02, &self->pid, G_LITTLE_ENDIAN, error)) return FALSE; } /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse_over_ip (FuRedfishSmbios *self, GBytes *fw, gsize offset, GError **error) { gsize bufsz = 0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); guint8 hostname_length = 0x0; guint8 service_ip_address_format = 0x0; guint16 service_ip_port = 0x0; guint8 service_ip_address[16] = { 0x0 }; /* port */ if (!fu_common_read_uint16_safe (buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port), &service_ip_port, G_LITTLE_ENDIAN, error)) return FALSE; fu_redfish_smbios_set_port (self, service_ip_port); /* IP address */ if (!fu_memcpy_safe (service_ip_address, sizeof(service_ip_address), 0x0, /* dst */ buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address), /* src */ sizeof(service_ip_address), error)) return FALSE; if (!fu_common_read_uint8_safe (buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format), &service_ip_address_format, error)) return FALSE; if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V4) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv4 (service_ip_address); fu_redfish_smbios_set_ip_addr (self, tmp); } else if (service_ip_address_format == REDFISH_IP_ADDRESS_FORMAT_V6) { g_autofree gchar *tmp = NULL; tmp = fu_redfish_common_buffer_to_ipv6 (service_ip_address); fu_redfish_smbios_set_ip_addr (self, tmp); } else { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "address format is invalid"); return FALSE; } /* hostname */ if (!fu_common_read_uint8_safe (buf, bufsz, offset + G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len), &hostname_length, error)) return FALSE; if (hostname_length > 0) { g_autofree gchar *hostname = g_malloc0 (hostname_length + 1); if (!fu_memcpy_safe ((guint8 *) hostname, hostname_length, 0x0, /* dst */ buf, bufsz, offset + sizeof(FuRedfishProtocolOverIp), /* src */ hostname_length, error)) return FALSE; fu_redfish_smbios_set_hostname (self, hostname); } /* success */ return TRUE; } static gboolean fu_redfish_smbios_parse (FuFirmware *firmware, GBytes *fw, guint64 addr_start, guint64 addr_end, FwupdInstallFlags flags, GError **error) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS (firmware); gsize bufsz = 0; gsize offset = 0; guint8 protocol_rcds = 0; const guint8 *buf = g_bytes_get_data (fw, &bufsz); /* check size */ if (bufsz < 0x09) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "SMBIOS entry too small: %" G_GSIZE_FORMAT, bufsz); return FALSE; } /* check type */ if (buf[0x0] != REDFISH_SMBIOS_TABLE_TYPE) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "not Management Controller Host Interface"); return FALSE; } if (buf[0x1] != bufsz) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INVALID_FILE, "size of table 0x%x does not match binary 0x%x", buf[0x1], (guint) bufsz); 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] > 0) { if (!fu_redfish_smbios_parse_interface_data (self, fw, 0x06, error)) return FALSE; } /* parse protocol records */ if (!fu_common_read_uint8_safe (buf, bufsz, 0x06 + buf[0x05], &protocol_rcds, error)) return FALSE; offset = 0x07 + buf[0x05]; g_debug ("protocol_rcds: %u", protocol_rcds); for (guint i = 0; i < protocol_rcds; i++) { guint8 protocol_id = 0; guint8 protocol_sz = 0; if (!fu_common_read_uint8_safe (buf, bufsz, offset, &protocol_id, error)) return FALSE; if (!fu_common_read_uint8_safe (buf, bufsz, offset + 0x1, &protocol_sz, error)) return FALSE; if (protocol_id == REDFISH_PROTOCOL_REDFISH_OVER_IP) { if (!fu_redfish_smbios_parse_over_ip (self, fw, offset + 0x2, error)) return FALSE; } else { g_debug ("ignoring protocol ID 0x%02x", protocol_id); } offset += protocol_sz + 1; } /* success */ return TRUE; } static GBytes * fu_redfish_smbios_write (FuFirmware *firmware, GError **error) { FuRedfishProtocolOverIp proto = { 0x0 }; FuRedfishSmbios *self = FU_REDFISH_SMBIOS (firmware); gsize hostname_sz = 0; g_autoptr(GByteArray) buf = g_byte_array_new (); if (self->hostname != NULL) hostname_sz = strlen (self->hostname); fu_byte_array_append_uint8 (buf, REDFISH_SMBIOS_TABLE_TYPE); fu_byte_array_append_uint8 (buf, 0x6D + hostname_sz); /* length */ fu_byte_array_append_uint16 (buf, 0x1234, G_LITTLE_ENDIAN); /* handle */ fu_byte_array_append_uint8 (buf, REDFISH_CONTROLLER_INTERFACE_TYPE_NETWORK_HOST); fu_byte_array_append_uint8 (buf, 0x09); /* iface datalen */ fu_byte_array_append_uint8 (buf, REDFISH_INTERFACE_TYPE_USB_NETWORK); /* iface */ fu_byte_array_append_uint16 (buf, self->vid, G_LITTLE_ENDIAN); /* iface:VID */ fu_byte_array_append_uint16 (buf, self->pid, G_LITTLE_ENDIAN); /* iface:PID */ fu_byte_array_append_uint8 (buf, 0x02); /* iface:serialsz */ fu_byte_array_append_uint8 (buf, 0x03); /* iType */ fu_byte_array_append_uint8 (buf, 'S'); /* iface:serial */ fu_byte_array_append_uint8 (buf, 'n'); /* iface:serial */ fu_byte_array_append_uint8 (buf, 0x1); /* nr protocol rcds */ /* protocol record */ fu_byte_array_append_uint8 (buf, REDFISH_PROTOCOL_REDFISH_OVER_IP); fu_byte_array_append_uint8 (buf, sizeof(FuRedfishProtocolOverIp) + hostname_sz); if (!fu_common_write_uint16_safe ((guint8 *) &proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_port), self->port, G_LITTLE_ENDIAN, error)) return NULL; if (!fu_common_write_uint8_safe ((guint8 *) &proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_address_format), REDFISH_IP_ADDRESS_FORMAT_V4, error)) return NULL; if (!fu_common_write_uint8_safe ((guint8 *) &proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_ip_assignment_type), REDFISH_IP_ASSIGNMENT_TYPE_STATIC, error)) return NULL; if (self->hostname != NULL) hostname_sz = strlen (self->hostname); if (hostname_sz > 0) { if (!fu_common_write_uint8_safe ((guint8 *) &proto, sizeof(proto), G_STRUCT_OFFSET(FuRedfishProtocolOverIp, service_hostname_len), hostname_sz, error)) { g_prefix_error (error, "cannot write length: "); return NULL; } } g_byte_array_append (buf, (guint8 *) &proto, sizeof(proto)); if (hostname_sz > 0) g_byte_array_append (buf, (guint8 *) self->hostname, hostname_sz); return g_byte_array_free_to_bytes (g_steal_pointer (&buf)); } static void fu_redfish_smbios_finalize (GObject *object) { FuRedfishSmbios *self = FU_REDFISH_SMBIOS (object); g_free (self->hostname); g_free (self->mac_addr); g_free (self->ip_addr); G_OBJECT_CLASS (fu_redfish_smbios_parent_class)->finalize (object); } static void fu_redfish_smbios_init (FuRedfishSmbios *self) { } static void fu_redfish_smbios_class_init (FuRedfishSmbiosClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); FuFirmwareClass *klass_firmware = FU_FIRMWARE_CLASS (klass); object_class->finalize = fu_redfish_smbios_finalize; klass_firmware->parse = fu_redfish_smbios_parse; klass_firmware->write = fu_redfish_smbios_write; klass_firmware->build = fu_redfish_smbios_build; klass_firmware->export = fu_redfish_smbios_export; } FuRedfishSmbios * fu_redfish_smbios_new (void) { return FU_REDFISH_SMBIOS (g_object_new (FU_TYPE_REDFISH_SMBIOS, NULL)); }