blob: 5ae0fc4d8ff480720e31df8acac5c1f0e21460ef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/** SMP - Simple Management Protocol. */
#include <assert.h>
#include <string.h>
#include "tinycbor/cbor.h"
#include "mgmt/endian.h"
#include "mgmt/mgmt.h"
#include "smp/smp.h"
static int
smp_align4(int x)
{
int rem;
rem = x % 4;
if (rem == 0) {
return x;
} else {
return x - rem + 4;
}
}
/**
* Converts a request opcode to its corresponding response opcode.
*/
static uint8_t
smp_rsp_op(uint8_t req_op)
{
if (req_op == MGMT_OP_READ) {
return MGMT_OP_READ_RSP;
} else {
return MGMT_OP_WRITE_RSP;
}
}
static void
smp_init_rsp_hdr(const struct mgmt_hdr *req_hdr, struct mgmt_hdr *rsp_hdr)
{
*rsp_hdr = (struct mgmt_hdr) {
.nh_len = 0,
.nh_flags = 0,
.nh_op = smp_rsp_op(req_hdr->nh_op),
.nh_group = req_hdr->nh_group,
.nh_seq = req_hdr->nh_seq,
.nh_id = req_hdr->nh_id,
};
}
static int
smp_read_hdr(struct smp_streamer *streamer, struct mgmt_hdr *dst_hdr)
{
struct cbor_decoder_reader *reader;
reader = streamer->mgmt_stmr.reader;
if (reader->message_size < sizeof *dst_hdr) {
return MGMT_ERR_EINVAL;
}
reader->cpy(reader, (char *)dst_hdr, 0, sizeof *dst_hdr);
return 0;
}
static int
smp_write_hdr(struct smp_streamer *streamer, const struct mgmt_hdr *src_hdr)
{
int rc;
rc = mgmt_streamer_write_at(&streamer->mgmt_stmr, 0, src_hdr,
sizeof *src_hdr);
return mgmt_err_from_cbor(rc);
}
static int
smp_build_err_rsp(struct smp_streamer *streamer,
const struct mgmt_hdr *req_hdr,
int status)
{
struct CborEncoder map;
struct mgmt_ctxt cbuf;
struct mgmt_hdr rsp_hdr;
int rc;
rc = mgmt_ctxt_init(&cbuf, &streamer->mgmt_stmr);
if (rc != 0) {
return rc;
}
smp_init_rsp_hdr(req_hdr, &rsp_hdr);
rc = smp_write_hdr(streamer, &rsp_hdr);
if (rc != 0) {
return rc;
}
rc = cbor_encoder_create_map(&cbuf.encoder, &map, CborIndefiniteLength);
if (rc != 0) {
return rc;
}
rc = mgmt_write_rsp_status(&cbuf, status);
if (rc != 0) {
return rc;
}
rc = cbor_encoder_close_container(&cbuf.encoder, &map);
if (rc != 0) {
return rc;
}
rsp_hdr.nh_len = cbor_encode_bytes_written(&cbuf.encoder) - MGMT_HDR_SIZE;
mgmt_hton_hdr(&rsp_hdr);
rc = smp_write_hdr(streamer, &rsp_hdr);
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Processes a single SMP request and generates a response payload (i.e.,
* everything after the management header). On success, the response payload
* is written to the supplied cbuf but not transmitted. On failure, no error
* response gets written; the caller is expected to build an error response
* from the return code.
*
* @param cbuf A cbuf containing the request and response
* buffer.
* @param req_hdr The management header belonging to the incoming
* request (host-byte order).
*
* @return A MGMT_ERR_[...] error code.
*/
static int
smp_handle_single_payload(struct mgmt_ctxt *cbuf,
const struct mgmt_hdr *req_hdr, bool *handler_found)
{
const struct mgmt_handler *handler;
mgmt_handler_fn *handler_fn;
struct CborEncoder payload_encoder;
int rc;
handler = mgmt_find_handler(req_hdr->nh_group, req_hdr->nh_id);
if (handler == NULL) {
return MGMT_ERR_ENOTSUP;
}
/* Begin response payload. Response fields are inserted into the root
* map as key value pairs.
*/
rc = cbor_encoder_create_map(&cbuf->encoder, &payload_encoder,
CborIndefiniteLength);
rc = mgmt_err_from_cbor(rc);
if (rc != 0) {
return rc;
}
switch (req_hdr->nh_op) {
case MGMT_OP_READ:
handler_fn = handler->mh_read;
break;
case MGMT_OP_WRITE:
handler_fn = handler->mh_write;
break;
default:
return MGMT_ERR_EINVAL;
}
if (handler_fn) {
*handler_found = true;
mgmt_evt(MGMT_EVT_OP_CMD_RECV, req_hdr->nh_group, req_hdr->nh_id, NULL);
rc = handler_fn(cbuf);
} else {
rc = MGMT_ERR_ENOTSUP;
}
if (rc != 0) {
return rc;
}
/* End response payload. */
rc = cbor_encoder_close_container(&cbuf->encoder, &payload_encoder);
return mgmt_err_from_cbor(rc);
}
/**
* Processes a single SMP request and generates a complete response (i.e.,
* header and payload). On success, the response is written using the supplied
* streamer but not transmitted. On failure, no error response gets written;
* the caller is expected to build an error response from the return code.
*
* @param streamer The SMP streamer to use for reading the request
* and writing the response.
* @param req_hdr The management header belonging to the incoming
* request (host-byte order).
*
* @return A MGMT_ERR_[...] error code.
*/
static int
smp_handle_single_req(struct smp_streamer *streamer,
const struct mgmt_hdr *req_hdr, bool *handler_found)
{
struct mgmt_ctxt cbuf;
struct mgmt_hdr rsp_hdr;
int rc;
rc = mgmt_ctxt_init(&cbuf, &streamer->mgmt_stmr);
if (rc != 0) {
return rc;
}
/* Write a dummy header to the beginning of the response buffer. Some
* fields will need to be fixed up later.
*/
smp_init_rsp_hdr(req_hdr, &rsp_hdr);
rc = smp_write_hdr(streamer, &rsp_hdr);
if (rc != 0) {
return rc;
}
/* Process the request and write the response payload. */
rc = smp_handle_single_payload(&cbuf, req_hdr, handler_found);
if (rc != 0) {
return rc;
}
/* Fix up the response header with the correct length. */
rsp_hdr.nh_len = cbor_encode_bytes_written(&cbuf.encoder) - MGMT_HDR_SIZE;
mgmt_hton_hdr(&rsp_hdr);
rc = smp_write_hdr(streamer, &rsp_hdr);
if (rc != 0) {
return rc;
}
return 0;
}
/**
* Attempts to transmit an SMP error response. This function consumes both
* supplied buffers.
*
* @param streamer The SMP streamer for building and transmitting
* the response.
* @param req_hdr The header of the request which elicited the
* error.
* @param req The buffer holding the request.
* @param rsp The buffer holding the response, or NULL if
* none was allocated.
* @param status The status to indicate in the error response.
*/
static void
smp_on_err(struct smp_streamer *streamer, const struct mgmt_hdr *req_hdr,
void *req, void *rsp, int status)
{
int rc;
/* Prefer the response buffer for holding the error response. If no
* response buffer was allocated, use the request buffer instead.
*/
if (rsp == NULL) {
rsp = req;
req = NULL;
}
/* Clear the partial response from the buffer, if any. */
mgmt_streamer_reset_buf(&streamer->mgmt_stmr, rsp);
mgmt_streamer_init_writer(&streamer->mgmt_stmr, rsp);
/* Build and transmit the error response. */
rc = smp_build_err_rsp(streamer, req_hdr, status);
if (rc == 0) {
streamer->tx_rsp_cb(streamer, rsp, streamer->mgmt_stmr.cb_arg);
rsp = NULL;
}
/* Free any extra buffers. */
mgmt_streamer_free_buf(&streamer->mgmt_stmr, req);
mgmt_streamer_free_buf(&streamer->mgmt_stmr, rsp);
}
/**
* Processes all SMP requests in an incoming packet. Requests are processed
* sequentially from the start of the packet to the end. Each response is sent
* individually in its own packet. If a request elicits an error response,
* processing of the packet is aborted. This function consumes the supplied
* request buffer regardless of the outcome.
*
* @param streamer The streamer to use for reading, writing, and
* transmitting.
* @param req A buffer containing the request packet.
*
* @return 0 on success, MGMT_ERR_[...] code on failure.
*/
int
smp_process_request_packet(struct smp_streamer *streamer, void *req)
{
struct mgmt_hdr req_hdr;
struct mgmt_evt_op_cmd_done_arg cmd_done_arg;
void *rsp;
bool valid_hdr, handler_found;
int rc;
rsp = NULL;
valid_hdr = true;
while (1) {
handler_found = false;
rc = mgmt_streamer_init_reader(&streamer->mgmt_stmr, req);
if (rc != 0) {
valid_hdr = false;
break;
}
/* Read the management header and strip it from the request. */
rc = smp_read_hdr(streamer, &req_hdr);
if (rc != 0) {
valid_hdr = false;
break;
}
mgmt_ntoh_hdr(&req_hdr);
mgmt_streamer_trim_front(&streamer->mgmt_stmr, req, MGMT_HDR_SIZE);
rsp = mgmt_streamer_alloc_rsp(&streamer->mgmt_stmr, req);
if (rsp == NULL) {
rc = MGMT_ERR_ENOMEM;
break;
}
rc = mgmt_streamer_init_writer(&streamer->mgmt_stmr, rsp);
if (rc != 0) {
break;
}
/* Process the request payload and build the response. */
rc = smp_handle_single_req(streamer, &req_hdr, &handler_found);
if (rc != 0) {
break;
}
/* Send the response. */
rc = streamer->tx_rsp_cb(streamer, rsp, streamer->mgmt_stmr.cb_arg);
rsp = NULL;
if (rc != 0) {
break;
}
/* Trim processed request to free up space for subsequent responses. */
mgmt_streamer_trim_front(&streamer->mgmt_stmr, req,
smp_align4(req_hdr.nh_len));
cmd_done_arg.err = MGMT_ERR_EOK;
mgmt_evt(MGMT_EVT_OP_CMD_DONE, req_hdr.nh_group, req_hdr.nh_id,
&cmd_done_arg);
}
if (rc != 0 && valid_hdr) {
smp_on_err(streamer, &req_hdr, req, rsp, rc);
if (handler_found) {
cmd_done_arg.err = rc;
mgmt_evt(MGMT_EVT_OP_CMD_DONE, req_hdr.nh_group, req_hdr.nh_id,
&cmd_done_arg);
}
return rc;
}
mgmt_streamer_free_buf(&streamer->mgmt_stmr, req);
mgmt_streamer_free_buf(&streamer->mgmt_stmr, rsp);
return 0;
}