blob: aa6afee551b432d85d25ba65b9c2c171e0c54a8c [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.
*/
#include <assert.h>
#include <string.h>
#include "os/mynewt.h"
#include "mem/mem.h"
#include "mgmt/mgmt.h"
#include "newtmgr/newtmgr.h"
#include "nmgr_os/nmgr_os.h"
#include "tinycbor/cbor.h"
#include "tinycbor/cbor_mbuf_writer.h"
#include "tinycbor/cbor_mbuf_reader.h"
/* Shared queue that newtmgr uses for work items. */
struct os_eventq *nmgr_evq;
/*
* cbor buffer for newtmgr
*/
static struct nmgr_cbuf {
struct mgmt_cbuf n_b;
struct cbor_mbuf_writer writer;
struct cbor_mbuf_reader reader;
struct os_mbuf *n_out_m;
} nmgr_task_cbuf;
struct os_eventq *
mgmt_evq_get(void)
{
return nmgr_evq;
}
void
mgmt_evq_set(struct os_eventq *evq)
{
nmgr_evq = evq;
}
static int
nmgr_cbuf_init(struct nmgr_cbuf *njb)
{
memset(njb, 0, sizeof(*njb));
return (0);
}
static struct nmgr_hdr *
nmgr_init_rsp(struct os_mbuf *m, struct nmgr_hdr *src)
{
struct nmgr_hdr *hdr;
hdr = (struct nmgr_hdr *) os_mbuf_extend(m, sizeof(struct nmgr_hdr));
if (!hdr) {
return NULL;
}
memcpy(hdr, src, sizeof(*hdr));
hdr->nh_len = 0;
hdr->nh_flags = 0;
hdr->nh_op = (src->nh_op == NMGR_OP_READ) ? NMGR_OP_READ_RSP :
NMGR_OP_WRITE_RSP;
hdr->nh_group = src->nh_group;
hdr->nh_seq = src->nh_seq;
hdr->nh_id = src->nh_id;
/* setup state for cbor encoding */
cbor_mbuf_writer_init(&nmgr_task_cbuf.writer, m);
cbor_encoder_init(&nmgr_task_cbuf.n_b.encoder, &nmgr_task_cbuf.writer.enc, 0);
nmgr_task_cbuf.n_out_m = m;
return hdr;
}
static void
nmgr_send_err_rsp(struct nmgr_transport *nt, struct os_mbuf *m,
struct nmgr_hdr *hdr, int status)
{
struct CborEncoder map;
int rc;
hdr = nmgr_init_rsp(m, hdr);
if (!hdr) {
os_mbuf_free_chain(m);
return;
}
rc = cbor_encoder_create_map(&nmgr_task_cbuf.n_b.encoder, &map,
CborIndefiniteLength);
if (rc != 0) {
return;
}
rc = mgmt_cbuf_setoerr(&nmgr_task_cbuf.n_b, status);
if (rc != 0) {
return;
}
rc = cbor_encoder_close_container(&nmgr_task_cbuf.n_b.encoder, &map);
if (rc != 0) {
return;
}
hdr->nh_len =
htons(cbor_encode_bytes_written(&nmgr_task_cbuf.n_b.encoder));
nt->nt_output(nt, nmgr_task_cbuf.n_out_m);
}
/**
* Allocates an mbuf to contain an outgoing response fragment.
*/
static struct os_mbuf *
nmgr_rsp_frag_alloc(uint16_t frag_size, void *arg)
{
struct os_mbuf *src_rsp;
struct os_mbuf *frag;
/* We need to duplicate the user header from the source response, as that
* is where transport-specific information is stored.
*/
src_rsp = arg;
frag = os_msys_get_pkthdr(frag_size, OS_MBUF_USRHDR_LEN(src_rsp));
if (frag != NULL) {
/* Copy the user header from the response into the fragment mbuf. */
memcpy(OS_MBUF_USRHDR(frag), OS_MBUF_USRHDR(src_rsp),
OS_MBUF_USRHDR_LEN(src_rsp));
}
return frag;
}
/**
* Sends a newtmgr response, fragmenting it as needed. The supplied response
* mbuf is consumed on success and in some failure cases. If the mbuf is
* consumed, the supplied pointer is set to NULL.
*
* This function prefers not to consume the supplied mbuf on failure. The
* reason for this is to allow the caller to reuse the mbuf for an error
* response.
*/
static int
nmgr_rsp_tx(struct nmgr_transport *nt, struct os_mbuf **rsp, uint16_t mtu)
{
struct os_mbuf *frag;
int rc;
while (*rsp != NULL) {
frag = mem_split_frag(rsp, mtu, nmgr_rsp_frag_alloc, *rsp);
if (frag == NULL) {
return MGMT_ERR_ENOMEM;
}
rc = nt->nt_output(nt, frag);
if (rc != 0) {
/* Output function already freed mbuf. */
return MGMT_ERR_EUNKNOWN;
}
}
return MGMT_ERR_EOK;
}
static void
nmgr_handle_req(struct nmgr_transport *nt, struct os_mbuf *req)
{
struct os_mbuf *rsp;
const struct mgmt_handler *handler;
CborEncoder payload_enc;
struct nmgr_hdr *rsp_hdr;
struct nmgr_hdr hdr;
int off;
uint16_t len;
uint16_t mtu;
int rc;
rsp_hdr = NULL;
rsp = os_msys_get_pkthdr(512, OS_MBUF_USRHDR_LEN(req));
if (!rsp) {
rc = os_mbuf_copydata(req, 0, sizeof(hdr), &hdr);
if (rc < 0) {
goto err_norsp;
}
rsp = req;
req = NULL;
goto err;
}
mtu = nt->nt_get_mtu(req);
if (mtu == 0) {
/* The transport cannot support a transmission right now. */
goto err_norsp;
}
/* Copy the request user header into the response. */
memcpy(OS_MBUF_USRHDR(rsp), OS_MBUF_USRHDR(req), OS_MBUF_USRHDR_LEN(req));
off = 0;
len = OS_MBUF_PKTHDR(req)->omp_len;
while (off < len) {
rc = os_mbuf_copydata(req, off, sizeof(hdr), &hdr);
if (rc < 0) {
rc = MGMT_ERR_EINVAL;
goto err_norsp;
}
hdr.nh_len = ntohs(hdr.nh_len);
handler = mgmt_find_handler(ntohs(hdr.nh_group), hdr.nh_id);
if (!handler) {
rc = MGMT_ERR_ENOENT;
goto err;
}
/* Build response header apriori. Then pass to the handlers
* to fill out the response data, and adjust length & flags.
*/
rsp_hdr = nmgr_init_rsp(rsp, &hdr);
if (!rsp_hdr) {
rc = MGMT_ERR_ENOMEM;
goto err_norsp;
}
cbor_mbuf_reader_init(&nmgr_task_cbuf.reader, req, sizeof(hdr));
cbor_parser_init(&nmgr_task_cbuf.reader.r, 0,
&nmgr_task_cbuf.n_b.parser, &nmgr_task_cbuf.n_b.it);
/* Begin response payload. Response fields are inserted into the root
* map as key value pairs.
*/
rc = cbor_encoder_create_map(&nmgr_task_cbuf.n_b.encoder, &payload_enc,
CborIndefiniteLength);
if (rc != 0) {
rc = MGMT_ERR_ENOMEM;
goto err;
}
if (hdr.nh_op == NMGR_OP_READ) {
if (handler->mh_read) {
rc = handler->mh_read(&nmgr_task_cbuf.n_b);
} else {
rc = MGMT_ERR_ENOENT;
}
} else if (hdr.nh_op == NMGR_OP_WRITE) {
if (handler->mh_write) {
rc = handler->mh_write(&nmgr_task_cbuf.n_b);
} else {
rc = MGMT_ERR_ENOENT;
}
} else {
rc = MGMT_ERR_EINVAL;
}
if (rc != 0) {
goto err;
}
/* End response payload. */
rc = cbor_encoder_close_container(&nmgr_task_cbuf.n_b.encoder,
&payload_enc);
if (rc != 0) {
rc = MGMT_ERR_ENOMEM;
goto err;
}
rsp_hdr->nh_len +=
cbor_encode_bytes_written(&nmgr_task_cbuf.n_b.encoder);
rsp_hdr->nh_len = htons(rsp_hdr->nh_len);
rc = nmgr_rsp_tx(nt, &rsp, mtu);
if (rc) {
/* If the entire mbuf was consumed by the transport, don't attempt
* to send an error response.
*/
if (rsp == NULL) {
goto err_norsp;
} else {
goto err;
}
}
off += sizeof(hdr) + OS_ALIGN(hdr.nh_len, 4);
}
os_mbuf_free_chain(rsp);
os_mbuf_free_chain(req);
return;
err:
/* Clear partially written response. */
os_mbuf_adj(rsp, OS_MBUF_PKTLEN(rsp));
nmgr_send_err_rsp(nt, rsp, &hdr, rc);
os_mbuf_free_chain(req);
return;
err_norsp:
os_mbuf_free_chain(rsp);
os_mbuf_free_chain(req);
return;
}
static void
nmgr_process(struct nmgr_transport *nt)
{
struct os_mbuf *m;
while (1) {
m = os_mqueue_get(&nt->nt_imq);
if (!m) {
break;
}
nmgr_handle_req(nt, m);
}
}
static void
nmgr_event_data_in(struct os_event *ev)
{
nmgr_process(ev->ev_arg);
}
int
nmgr_transport_init(struct nmgr_transport *nt,
nmgr_transport_out_func_t output_func,
nmgr_transport_get_mtu_func_t get_mtu_func)
{
int rc;
nt->nt_output = output_func;
nt->nt_get_mtu = get_mtu_func;
rc = os_mqueue_init(&nt->nt_imq, nmgr_event_data_in, nt);
if (rc != 0) {
goto err;
}
return (0);
err:
return (rc);
}
/**
* Transfers an incoming request to the newtmgr task. The caller relinquishes
* ownership of the supplied mbuf upon calling this function, whether this
* function succeeds or fails.
*
* @param nt The transport that the request was received
* over.
* @param req An mbuf containing the newtmgr request.
*
* @return 0 on success; nonzero on failure.
*/
int
nmgr_rx_req(struct nmgr_transport *nt, struct os_mbuf *req)
{
int rc;
rc = os_mqueue_put(&nt->nt_imq, mgmt_evq_get(), req);
if (rc != 0) {
os_mbuf_free_chain(req);
}
return rc;
}
void
nmgr_pkg_init(void)
{
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
rc = nmgr_os_groups_register();
SYSINIT_PANIC_ASSERT(rc == 0);
nmgr_cbuf_init(&nmgr_task_cbuf);
mgmt_evq_set(os_eventq_dflt_get());
}