blob: 82b38f43617c8aec18874ac95e16e95d28b21f94 [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 "os/mynewt.h"
#if MYNEWT_VAL(SHELL_MGMT)
#include <stddef.h>
#include "base64/base64.h"
#include "crc/crc16.h"
#include "console/console.h"
#include "mgmt/mgmt.h"
#include "shell/shell.h"
#include "shell_priv.h"
/* NLIP packets sent over serial are fragmented into frames of 127 bytes or
* fewer. This 127-byte maximum applies to the entire frame, including header,
* CRC, and terminating newline.
*/
#define MGMT_NLIP_MAX_FRAME 127
static shell_nlip_input_func_t g_shell_nlip_in_func;
static void *g_shell_nlip_in_arg;
static struct os_mqueue g_shell_nlip_mq;
static struct os_mbuf *g_nlip_mbuf;
static uint16_t g_nlip_expected_len;
void
shell_nlip_clear_pkt(void)
{
if (g_nlip_mbuf) {
os_mbuf_free_chain(g_nlip_mbuf);
g_nlip_mbuf = NULL;
}
g_nlip_expected_len = 0;
}
int
shell_nlip_process(char *data, int len)
{
uint16_t copy_len;
int rc;
struct os_mbuf *m;
uint16_t crc;
/* Trim trailing newline. */
if (len >= 1 && data[len - 1] == '\n') {
data[len - 1] = '\0';
len--;
}
rc = base64_decode(data, data);
if (rc < 0) {
goto err;
}
len = rc;
if (g_nlip_mbuf == NULL) {
if (len < 2) {
rc = -1;
goto err;
}
g_nlip_expected_len = ntohs(*(uint16_t *) data);
g_nlip_mbuf = os_msys_get_pkthdr(g_nlip_expected_len, 0);
if (!g_nlip_mbuf) {
rc = -1;
goto err;
}
data += sizeof(uint16_t);
len -= sizeof(uint16_t);
}
copy_len = min(g_nlip_expected_len - OS_MBUF_PKTHDR(g_nlip_mbuf)->omp_len,
len);
rc = os_mbuf_copyinto(g_nlip_mbuf, OS_MBUF_PKTHDR(g_nlip_mbuf)->omp_len,
data, copy_len);
if (rc != 0) {
goto err;
}
if (OS_MBUF_PKTHDR(g_nlip_mbuf)->omp_len == g_nlip_expected_len) {
if (g_shell_nlip_in_func) {
crc = CRC16_INITIAL_CRC;
for (m = g_nlip_mbuf; m; m = SLIST_NEXT(m, om_next)) {
crc = crc16_ccitt(crc, m->om_data, m->om_len);
}
if (crc == 0 && g_nlip_expected_len >= sizeof(crc)) {
os_mbuf_adj(g_nlip_mbuf, -sizeof(crc));
g_shell_nlip_in_func(g_nlip_mbuf, g_shell_nlip_in_arg);
} else {
os_mbuf_free_chain(g_nlip_mbuf);
}
} else {
os_mbuf_free_chain(g_nlip_mbuf);
}
g_nlip_mbuf = NULL;
g_nlip_expected_len = 0;
}
return (0);
err:
return (rc);
}
static int
shell_nlip_mtx(struct os_mbuf *m)
{
#define SHELL_NLIP_MTX_BUF_SIZE (12)
uint8_t readbuf[SHELL_NLIP_MTX_BUF_SIZE];
char encodebuf[BASE64_ENCODE_SIZE(SHELL_NLIP_MTX_BUF_SIZE)];
char pkt_seq[3] = { '\n', SHELL_NLIP_PKT_START1, SHELL_NLIP_PKT_START2 };
char esc_seq[2] = { SHELL_NLIP_DATA_START1, SHELL_NLIP_DATA_START2 };
uint16_t totlen;
uint16_t dlen;
uint16_t off;
uint16_t crc;
int rb_off;
int elen;
uint16_t bodylen;
uint16_t newbodylen;
int rc;
struct os_mbuf *tmp;
void *ptr;
/* Convert the mbuf into a packet.
*
* starts with 06 09
* base64 encode:
* - total packet length (uint16_t)
* - data
* - crc
* base64 encoded data must be less than 122 bytes per line to
* avoid overflows and adhere to convention.
*
* continuation packets are preceded by 04 20 until the entire
* buffer has been sent.
*/
crc = CRC16_INITIAL_CRC;
for (tmp = m; tmp; tmp = SLIST_NEXT(tmp, om_next)) {
crc = crc16_ccitt(crc, tmp->om_data, tmp->om_len);
}
crc = htons(crc);
ptr = os_mbuf_extend(m, sizeof(crc));
if (!ptr) {
rc = -1;
goto err;
}
memcpy(ptr, &crc, sizeof(crc));
totlen = OS_MBUF_PKTHDR(m)->omp_len;
off = 0;
bodylen = 0;
rb_off = 0;
rc = console_lock(OS_TICKS_PER_SEC);
if (rc != OS_OK) {
goto err;
}
/* Start a packet */
console_write(pkt_seq, sizeof(pkt_seq));
/* Encode the packet length. */
dlen = htons(totlen);
memcpy(readbuf, &dlen, sizeof(dlen));
rb_off += 2;
while (totlen > 0) {
/* Calculate number of source bytes to include in this chunk. */
dlen = min(SHELL_NLIP_MTX_BUF_SIZE - rb_off, totlen);
/* If appending this chunk to the current line causes the line to be
* too long, terminate the line before emitting the chunk.
*
* Total line length:
* 2 + // Frame header.
* bodylen + // Body bytes already emitted.
* BASE64_ENCODE_SIZE(...) + // Next chunk.
* 1 // Terminating newline.
*/
newbodylen = 2 + bodylen + BASE64_ENCODE_SIZE(dlen + rb_off) + 1;
if (newbodylen > MGMT_NLIP_MAX_FRAME) {
/* The chunk won't fit. Terminate the current line. */
elen = base64_pad(encodebuf, bodylen);
console_write(encodebuf, elen);
console_write("\n", 1);
/* Begin the next frame. */
console_write(esc_seq, sizeof(esc_seq));
bodylen = 0;
} else {
/* The chunk will fit. Emit it. */
rc = os_mbuf_copydata(m, off, dlen, readbuf + rb_off);
if (rc != 0) {
goto end;
}
elen = base64_encode(readbuf, dlen + rb_off, encodebuf, 0);
console_write(encodebuf, elen);
bodylen += elen;
totlen -= dlen;
off += dlen;
}
rb_off = 0;
}
/* The final line was never terminated. Terminate it now. */
elen = base64_pad(encodebuf, bodylen);
console_write(encodebuf, elen);
console_write("\n", 1);
end:
(void)console_unlock();
err:
return (rc);
}
int
shell_nlip_input_register(shell_nlip_input_func_t nf, void *arg)
{
g_shell_nlip_in_func = nf;
g_shell_nlip_in_arg = arg;
return (0);
}
int
shell_nlip_output(struct os_mbuf *m)
{
return os_mqueue_put(&g_shell_nlip_mq, os_eventq_dflt_get(), m);
}
static void
shell_event_data_in(struct os_event *ev)
{
struct os_mbuf *m;
/* Copy data out of the mbuf 12 bytes at a time and write it to
* the console.
*/
while (1) {
m = os_mqueue_get(&g_shell_nlip_mq);
if (!m) {
break;
}
(void) shell_nlip_mtx(m);
os_mbuf_free_chain(m);
}
}
void
shell_nlip_init(void)
{
os_mqueue_init(&g_shell_nlip_mq, shell_event_data_in, NULL);
}
#endif