core/cmd_interface/cmd_interface.c (189 lines of code) (raw):

// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. #include <stdbool.h> #include <stddef.h> #include <stdint.h> #include <string.h> #include "cerberus_protocol.h" #include "cmd_interface.h" #include "mctp/mctp_base_protocol.h" /** * Configure the message structure to support receiving a new message in the data buffer. The data * pointer is not modified, but the descriptor for that buffer is reset. * * @param message The message instance to initialize. * @param source_eid EID to assign as the message source. * @param source_addr Bus address to assign as the message source. * @param target_eid EID to assign as the message target. * @param channel_id Identifier for the command channel receiving the message. */ void cmd_interface_msg_new_message (struct cmd_interface_msg *message, uint8_t source_eid, uint8_t source_addr, uint8_t target_eid, int channel_id) { if (message != NULL) { message->length = 0; message->payload = message->data; message->payload_length = 0; message->source_eid = source_eid; message->source_addr = source_addr; message->target_eid = target_eid; message->is_encrypted = false; message->crypto_timeout = false; message->channel_id = channel_id; } } /** * Add new payload data to the message buffer. The new data will be appended to the existing * payload location. * * The data will be copied and the message length updated. There are no length checks performed * before the copy, so the caller must ensure there is sufficient space in the message buffer for * the additional payload data. * * @param message The message to update. * @param data The payload data to copy into the message buffer. * @param length Length of the payload data. */ void cmd_interface_msg_add_payload_data (struct cmd_interface_msg *message, const uint8_t *data, size_t length) { if ((message == NULL) || (data == NULL) || (length == 0)) { return; } memcpy (&message->payload[message->payload_length], data, length); message->length += length; message->payload_length += length; } /** * Set the length of a message payload without any additional protocol headers. Protocol headers * will need to be added using {@link cmd_interface_msg_add_protocol_header}. * * The length provided is not validated in any way. The caller must ensure the length is valid for * the data in the message buffer. * * @param message The message to update. * @param length Length of the message. */ void cmd_interface_msg_set_message_payload_length (struct cmd_interface_msg *message, size_t length) { if (message != NULL) { message->length = length; message->payload_length = length; } } /** * Update the message payload to remove a layer of protocol headers. The message structure must * have been properly initialized with {@link cmd_interface_msg_new_message} and have message data * in the buffer before making this call. * * @param message The message to update. * @param header_length Size of the protocol header. */ void cmd_interface_msg_remove_protocol_header (struct cmd_interface_msg *message, size_t header_length) { if (message != NULL) { if (header_length > message->payload_length) { header_length = message->payload_length; } message->payload += header_length; message->payload_length -= header_length; } } /** * Update the message payload to add a layer of protocol headers. The message structure must have * been properly initialized with {@link cmd_interface_msg_new_message}. * * When adding protocol headers, both the payload and overall data lengths will be increased, unless * these lengths are different from each other. * * @param message The message to update. * @param header_length Size of the protocol header. */ void cmd_interface_msg_add_protocol_header (struct cmd_interface_msg *message, size_t header_length) { size_t hdr_space; if (message != NULL) { hdr_space = message->payload - message->data; if (hdr_space < header_length) { header_length = hdr_space; } message->payload -= header_length; /* If both lengths are the same, keep them in sync with each other. If they start off * different, don't update the raw message length. */ if (message->length == message->payload_length) { message->length += header_length; } message->payload_length += header_length; } } /** * Determine the length of protocol header data added to the message. * * @param message The message to query. * * @return Length of the protocol header on the message data. */ size_t cmd_interface_msg_get_protocol_length (const struct cmd_interface_msg *message) { if (message == NULL) { return 0; } if (message->payload < message->data) { return 0; } return (message->payload - message->data); } /** * Determine the maximum data length allowed for response messages when building the response in the * message payload buffer. This will be the maximum message payload length, excluding protocol * headers. * * @param message The message to query. * * @return Maximum allowed length of payload response data. */ size_t cmd_interface_msg_get_max_response (const struct cmd_interface_msg *message) { size_t length; if (message == NULL) { return 0; } length = cmd_interface_msg_get_protocol_length (message); if (message->max_response >= length) { length = message->max_response - length; } else { /* This should never happen. This condition represents an improperly constructed message * descriptor, since there should always at least be room for the protocol headers in the * response message. */ length = 0; } return length; } /** * Set the maximum data length allowed for responses messages that can be constructed in the message * payload buffer. * * This will only reduce the maximum response length for the message. If the current maximum * response length is less than the requested setting, the length will not be changed. * * @param message The message to update. * @param max_response The maximum message response payload to set. This value represents the * maximum payload that can be added, so it must exclude the length of any protocol headers * preceding the payload data in the message buffer. */ void cmd_interface_msg_set_max_response (struct cmd_interface_msg *message, size_t max_response) { if (message != NULL) { max_response += cmd_interface_msg_get_protocol_length (message); if (max_response < message->max_response) { message->max_response = max_response; } } } #ifdef CMD_SUPPORT_ENCRYPTED_SESSIONS /** * Determine if received request is encrypted from header. * * @param intf The command interface that will process the request. * @param request The request being processed. * * @return 0 if the request is not encrypted, 1 if request is encrypted or an error code. */ static int cmd_interface_is_request_encrypted (const struct cmd_interface *intf, struct cmd_interface_msg *request) { struct cerberus_protocol_header *header; if ((intf == NULL) || (request == NULL)) { return CMD_HANDLER_INVALID_ARGUMENT; } header = (struct cerberus_protocol_header*) request->data; if ((request->length < CERBERUS_PROTOCOL_MIN_MSG_LEN) || (header->msg_type != MCTP_BASE_PROTOCOL_MSG_TYPE_VENDOR_DEF) || (header->pci_vendor_id != CERBERUS_PROTOCOL_MSFT_PCI_VID)) { return 0; } return header->crypt; } #endif /** * Pre-process received Cerberus protocol message. * * TODO: Deprecate use of this function and remove it. Use the cmd_interface_protocol handlers * for processing Cerberus messages instead. * * @param intf The command interface that will process the message. * @param message The message being processed. * @param command_id Pointer to hold command ID of incoming message. * @param command_set Pointer to hold command set of incoming message. * @param decrypt Flag indicating whether to decrypt incoming message if encrypted. * @param rsvd_zero Flag indicating if the reserved bits must be set to zero. * * @return 0 if the message was successfully processed or an error code. */ int cmd_interface_process_cerberus_protocol_message (const struct cmd_interface *intf, struct cmd_interface_msg *message, uint8_t *command_id, uint8_t *command_set, bool decrypt, bool rsvd_zero) { struct cerberus_protocol_header *header; if (message == NULL) { return CMD_HANDLER_INVALID_ARGUMENT; } message->crypto_timeout = false; if ((intf == NULL) || (command_id == NULL) || (command_set == NULL)) { return CMD_HANDLER_INVALID_ARGUMENT; } message->is_encrypted = false; header = (struct cerberus_protocol_header*) message->data; if (message->length < CERBERUS_PROTOCOL_MIN_MSG_LEN) { return CMD_HANDLER_PAYLOAD_TOO_SHORT; } if ((header->msg_type != (MCTP_BASE_PROTOCOL_MSG_TYPE_VENDOR_DEF)) || (header->integrity_check == 1) || (header->pci_vendor_id != CERBERUS_PROTOCOL_MSFT_PCI_VID)) { return CMD_HANDLER_UNSUPPORTED_MSG; } if (rsvd_zero) { if ((header->reserved1 != 0) || (header->reserved2 != 0)) { return CMD_HANDLER_RSVD_NOT_ZERO; } } *command_id = header->command; *command_set = header->rq; if (header->crypt && decrypt) { #ifdef CMD_SUPPORT_ENCRYPTED_SESSIONS if (intf->session) { int status = intf->session->decrypt_message (intf->session, message); if (status != 0) { return status; } *command_id = header->command; message->max_response -= SESSION_MANAGER_TRAILER_LEN; message->is_encrypted = true; } else #endif { return CMD_HANDLER_ENCRYPTION_UNSUPPORTED; } } return 0; } /** * Process generated response. * * TODO: Deprecate use of this function and remove it. Use the cmd_interface_protocol handlers * for processing Cerberus messages instead. * * @param intf The command interface that will process the response. * @param response The response being processed. * * @return 0 if the response was successfully processed or an error code. */ int cmd_interface_prepare_response (const struct cmd_interface *intf, struct cmd_interface_msg *response) { int status = 0; if ((response == NULL) || (intf == NULL)) { return CMD_HANDLER_INVALID_ARGUMENT; } #ifdef CMD_SUPPORT_ENCRYPTED_SESSIONS if (response->is_encrypted) { response->max_response += SESSION_MANAGER_TRAILER_LEN; status = cmd_interface_is_request_encrypted (intf, response); if (ROT_IS_ERROR (status)) { return status; } if (!status) { return 0; } if (intf->session == NULL) { return CMD_HANDLER_ENCRYPTION_UNSUPPORTED; } status = intf->session->encrypt_message (intf->session, response); } #endif return status; }