|  | // SPDX-License-Identifier: GPL-2.0+ | 
|  | /* | 
|  | * SSH message parser. | 
|  | * | 
|  | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> | 
|  | */ | 
|  |  | 
|  | #include <asm/unaligned.h> | 
|  | #include <linux/compiler.h> | 
|  | #include <linux/device.h> | 
|  | #include <linux/types.h> | 
|  |  | 
|  | #include <linux/surface_aggregator/serial_hub.h> | 
|  | #include "ssh_parser.h" | 
|  |  | 
|  | /** | 
|  | * sshp_validate_crc() - Validate a CRC in raw message data. | 
|  | * @src: The span of data over which the CRC should be computed. | 
|  | * @crc: The pointer to the expected u16 CRC value. | 
|  | * | 
|  | * Computes the CRC of the provided data span (@src), compares it to the CRC | 
|  | * stored at the given address (@crc), and returns the result of this | 
|  | * comparison, i.e. %true if equal. This function is intended to run on raw | 
|  | * input/message data. | 
|  | * | 
|  | * Return: Returns %true if the computed CRC matches the stored CRC, %false | 
|  | * otherwise. | 
|  | */ | 
|  | static bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) | 
|  | { | 
|  | u16 actual = ssh_crc(src->ptr, src->len); | 
|  | u16 expected = get_unaligned_le16(crc); | 
|  |  | 
|  | return actual == expected; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. | 
|  | * @src: The data span to check the start of. | 
|  | */ | 
|  | static bool sshp_starts_with_syn(const struct ssam_span *src) | 
|  | { | 
|  | return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * sshp_find_syn() - Find SSH SYN bytes in the given data span. | 
|  | * @src: The data span to search in. | 
|  | * @rem: The span (output) indicating the remaining data, starting with SSH | 
|  | *       SYN bytes, if found. | 
|  | * | 
|  | * Search for SSH SYN bytes in the given source span. If found, set the @rem | 
|  | * span to the remaining data, starting with the first SYN bytes and capped by | 
|  | * the source span length, and return %true. This function does not copy any | 
|  | * data, but rather only sets pointers to the respective start addresses and | 
|  | * length values. | 
|  | * | 
|  | * If no SSH SYN bytes could be found, set the @rem span to the zero-length | 
|  | * span at the end of the source span and return %false. | 
|  | * | 
|  | * If partial SSH SYN bytes could be found at the end of the source span, set | 
|  | * the @rem span to cover these partial SYN bytes, capped by the end of the | 
|  | * source span, and return %false. This function should then be re-run once | 
|  | * more data is available. | 
|  | * | 
|  | * Return: Returns %true if a complete SSH SYN sequence could be found, | 
|  | * %false otherwise. | 
|  | */ | 
|  | bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) | 
|  | { | 
|  | size_t i; | 
|  |  | 
|  | for (i = 0; i < src->len - 1; i++) { | 
|  | if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { | 
|  | rem->ptr = src->ptr + i; | 
|  | rem->len = src->len - i; | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { | 
|  | rem->ptr = src->ptr + src->len - 1; | 
|  | rem->len = 1; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | rem->ptr = src->ptr + src->len; | 
|  | rem->len = 0; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * sshp_parse_frame() - Parse SSH frame. | 
|  | * @dev: The device used for logging. | 
|  | * @source: The source to parse from. | 
|  | * @frame: The parsed frame (output). | 
|  | * @payload: The parsed payload (output). | 
|  | * @maxlen: The maximum supported message length. | 
|  | * | 
|  | * Parses and validates a SSH frame, including its payload, from the given | 
|  | * source. Sets the provided @frame pointer to the start of the frame and | 
|  | * writes the limits of the frame payload to the provided @payload span | 
|  | * pointer. | 
|  | * | 
|  | * This function does not copy any data, but rather only validates the message | 
|  | * data and sets pointers (and length values) to indicate the respective parts. | 
|  | * | 
|  | * If no complete SSH frame could be found, the frame pointer will be set to | 
|  | * the %NULL pointer and the payload span will be set to the null span (start | 
|  | * pointer %NULL, size zero). | 
|  | * | 
|  | * Return: Returns zero on success or if the frame is incomplete, %-ENOMSG if | 
|  | * the start of the message is invalid, %-EBADMSG if any (frame-header or | 
|  | * payload) CRC is invalid, or %-EMSGSIZE if the SSH message is bigger than | 
|  | * the maximum message length specified in the @maxlen parameter. | 
|  | */ | 
|  | int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, | 
|  | struct ssh_frame **frame, struct ssam_span *payload, | 
|  | size_t maxlen) | 
|  | { | 
|  | struct ssam_span sf; | 
|  | struct ssam_span sp; | 
|  |  | 
|  | /* Initialize output. */ | 
|  | *frame = NULL; | 
|  | payload->ptr = NULL; | 
|  | payload->len = 0; | 
|  |  | 
|  | if (!sshp_starts_with_syn(source)) { | 
|  | dev_warn(dev, "rx: parser: invalid start of frame\n"); | 
|  | return -ENOMSG; | 
|  | } | 
|  |  | 
|  | /* Check for minimum packet length. */ | 
|  | if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { | 
|  | dev_dbg(dev, "rx: parser: not enough data for frame\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Pin down frame. */ | 
|  | sf.ptr = source->ptr + sizeof(u16); | 
|  | sf.len = sizeof(struct ssh_frame); | 
|  |  | 
|  | /* Validate frame CRC. */ | 
|  | if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { | 
|  | dev_warn(dev, "rx: parser: invalid frame CRC\n"); | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | /* Ensure packet does not exceed maximum length. */ | 
|  | sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); | 
|  | if (unlikely(SSH_MESSAGE_LENGTH(sp.len) > maxlen)) { | 
|  | dev_warn(dev, "rx: parser: frame too large: %llu bytes\n", | 
|  | SSH_MESSAGE_LENGTH(sp.len)); | 
|  | return -EMSGSIZE; | 
|  | } | 
|  |  | 
|  | /* Pin down payload. */ | 
|  | sp.ptr = sf.ptr + sf.len + sizeof(u16); | 
|  |  | 
|  | /* Check for frame + payload length. */ | 
|  | if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { | 
|  | dev_dbg(dev, "rx: parser: not enough data for payload\n"); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /* Validate payload CRC. */ | 
|  | if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { | 
|  | dev_warn(dev, "rx: parser: invalid payload CRC\n"); | 
|  | return -EBADMSG; | 
|  | } | 
|  |  | 
|  | *frame = (struct ssh_frame *)sf.ptr; | 
|  | *payload = sp; | 
|  |  | 
|  | dev_dbg(dev, "rx: parser: valid frame found (type: %#04x, len: %u)\n", | 
|  | (*frame)->type, (*frame)->len); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * sshp_parse_command() - Parse SSH command frame payload. | 
|  | * @dev: The device used for logging. | 
|  | * @source: The source to parse from. | 
|  | * @command: The parsed command (output). | 
|  | * @command_data: The parsed command data/payload (output). | 
|  | * | 
|  | * Parses and validates a SSH command frame payload. Sets the @command pointer | 
|  | * to the command header and the @command_data span to the command data (i.e. | 
|  | * payload of the command). This will result in a zero-length span if the | 
|  | * command does not have any associated data/payload. This function does not | 
|  | * check the frame-payload-type field, which should be checked by the caller | 
|  | * before calling this function. | 
|  | * | 
|  | * The @source parameter should be the complete frame payload, e.g. returned | 
|  | * by the sshp_parse_frame() command. | 
|  | * | 
|  | * This function does not copy any data, but rather only validates the frame | 
|  | * payload data and sets pointers (and length values) to indicate the | 
|  | * respective parts. | 
|  | * | 
|  | * Return: Returns zero on success or %-ENOMSG if @source does not represent a | 
|  | * valid command-type frame payload, i.e. is too short. | 
|  | */ | 
|  | int sshp_parse_command(const struct device *dev, const struct ssam_span *source, | 
|  | struct ssh_command **command, | 
|  | struct ssam_span *command_data) | 
|  | { | 
|  | /* Check for minimum length. */ | 
|  | if (unlikely(source->len < sizeof(struct ssh_command))) { | 
|  | *command = NULL; | 
|  | command_data->ptr = NULL; | 
|  | command_data->len = 0; | 
|  |  | 
|  | dev_err(dev, "rx: parser: command payload is too short\n"); | 
|  | return -ENOMSG; | 
|  | } | 
|  |  | 
|  | *command = (struct ssh_command *)source->ptr; | 
|  | command_data->ptr = source->ptr + sizeof(struct ssh_command); | 
|  | command_data->len = source->len - sizeof(struct ssh_command); | 
|  |  | 
|  | dev_dbg(dev, "rx: parser: valid command found (tc: %#04x, cid: %#04x)\n", | 
|  | (*command)->tc, (*command)->cid); | 
|  |  | 
|  | return 0; | 
|  | } |