/* * 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 #include #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; }