324 lines
8.8 KiB
C++
324 lines
8.8 KiB
C++
/*
|
|
; Project: Open Vehicle Monitor System
|
|
; Date: 14th March 2017
|
|
;
|
|
; Changes:
|
|
; 1.0 Initial release
|
|
;
|
|
; (C) 2011 Michael Stegen / Stegen Electronics
|
|
; (C) 2011-2017 Mark Webb-Johnson
|
|
; (C) 2011 Sonny Chen @ EPRO/DX
|
|
;
|
|
; Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
; of this software and associated documentation files (the "Software"), to deal
|
|
; in the Software without restriction, including without limitation the rights
|
|
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
; copies of the Software, and to permit persons to whom the Software is
|
|
; furnished to do so, subject to the following conditions:
|
|
;
|
|
; The above copyright notice and this permission notice shall be included in
|
|
; all copies or substantial portions of the Software.
|
|
;
|
|
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
; THE SOFTWARE.
|
|
*/
|
|
#define LWIP_POSIX_SOCKETS_IO_NAMES 0
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h> // Required for libtelnet.h
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <lwip/def.h>
|
|
#include <lwip/sockets.h>
|
|
#undef bind
|
|
#include "freertos/queue.h"
|
|
#include "ovms_log.h"
|
|
#include "ovms_events.h"
|
|
#include "console_telnet.h"
|
|
#include "ovms_netmanager.h"
|
|
|
|
//#define LOGEVENTS
|
|
#ifdef LOGEVENTS
|
|
static const char *eventToString(telnet_event_type_t type);
|
|
#endif
|
|
|
|
static const char *tag = "telnet";
|
|
static const char newline = '\n';
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Class OvmsTelnet
|
|
//-----------------------------------------------------------------------------
|
|
|
|
OvmsTelnet MyTelnet __attribute__ ((init_priority (8300)));
|
|
|
|
static void MongooseHandler(struct mg_connection *nc, int ev, void *p)
|
|
{
|
|
MyTelnet.EventHandler(nc, ev, p);
|
|
}
|
|
|
|
void OvmsTelnet::EventHandler(struct mg_connection *nc, int ev, void *p)
|
|
{
|
|
//ESP_LOGI(tag, "Event %d conn %p, data %p", ev, nc, p);
|
|
switch (ev)
|
|
{
|
|
case MG_EV_ACCEPT:
|
|
{
|
|
ConsoleTelnet* child = new ConsoleTelnet(nc);
|
|
nc->user_data = child;
|
|
break;
|
|
}
|
|
|
|
case MG_EV_POLL:
|
|
{
|
|
ConsoleTelnet* child = (ConsoleTelnet*)nc->user_data;
|
|
if (child)
|
|
child->Poll(0);
|
|
}
|
|
break;
|
|
|
|
case MG_EV_RECV:
|
|
{
|
|
ConsoleTelnet* child = (ConsoleTelnet*)nc->user_data;
|
|
child->Receive();
|
|
child->Poll(0);
|
|
}
|
|
break;
|
|
|
|
case MG_EV_CLOSE:
|
|
{
|
|
ConsoleTelnet* child = (ConsoleTelnet*)nc->user_data;
|
|
if (child)
|
|
delete child;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
OvmsTelnet::OvmsTelnet()
|
|
{
|
|
ESP_LOGI(tag, "Initialising Telnet (8300)");
|
|
|
|
m_running = false;
|
|
|
|
using std::placeholders::_1;
|
|
using std::placeholders::_2;
|
|
MyEvents.RegisterEvent(tag,"network.mgr.init", std::bind(&OvmsTelnet::NetManInit, this, _1, _2));
|
|
MyEvents.RegisterEvent(tag,"network.mgr.stop", std::bind(&OvmsTelnet::NetManStop, this, _1, _2));
|
|
}
|
|
|
|
void OvmsTelnet::NetManInit(std::string event, void* data)
|
|
{
|
|
// Only initialise server for WIFI connections
|
|
// TODO: Disabled as this introduces a network interface ordering issue. It
|
|
// seems that the correct way to do this is to always start the mongoose
|
|
// listener, but to filter incoming connections to check that the
|
|
// destination address is a Wifi interface address.
|
|
// if (!(MyNetManager.m_connected_wifi || MyNetManager.m_wifi_ap))
|
|
// return;
|
|
|
|
m_running = true;
|
|
|
|
ESP_LOGI(tag, "Launching Telnet Server");
|
|
struct mg_mgr* mgr = MyNetManager.GetMongooseMgr();
|
|
mg_connection* nc = mg_bind(mgr, ":23", MongooseHandler);
|
|
if (nc)
|
|
nc->user_data = NULL;
|
|
else
|
|
ESP_LOGE(tag, "Launching Telnet Server failed");
|
|
}
|
|
|
|
void OvmsTelnet::NetManStop(std::string event, void* data)
|
|
{
|
|
if (m_running)
|
|
{
|
|
ESP_LOGI(tag, "Stopping Telnet Server");
|
|
m_running = false;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Class ConsoleTelnet
|
|
//-----------------------------------------------------------------------------
|
|
|
|
ConsoleTelnet::ConsoleTelnet(struct mg_connection* nc)
|
|
{
|
|
m_connection = nc;
|
|
m_queue = xQueueCreate(100, sizeof(Event));
|
|
|
|
static const telnet_telopt_t options[] =
|
|
{
|
|
{ TELNET_TELOPT_ECHO, TELNET_WILL, TELNET_DONT },
|
|
{ TELNET_TELOPT_LINEMODE, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_SGA, TELNET_WILL, TELNET_DO },
|
|
{ TELNET_TELOPT_TTYPE, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_COMPRESS2, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_ZMP, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_MSSP, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_BINARY, TELNET_WONT, TELNET_DONT },
|
|
{ TELNET_TELOPT_NAWS, TELNET_WONT, TELNET_DONT },
|
|
{ -1, 0, 0 }
|
|
};
|
|
m_telnet = telnet_init(options, TelnetCallback, TELNET_FLAG_NVT_EOL, (void*)this);
|
|
telnet_negotiate(m_telnet, TELNET_WILL, TELNET_TELOPT_ECHO);
|
|
Initialize("Telnet");
|
|
}
|
|
|
|
// This destructor is only called by NetManTask deleting the ConsoleTelnet child.
|
|
|
|
ConsoleTelnet::~ConsoleTelnet()
|
|
{
|
|
telnet_t *telnet = m_telnet;
|
|
m_telnet = NULL;
|
|
telnet_free(telnet);
|
|
vQueueDelete(m_queue);
|
|
}
|
|
|
|
void ConsoleTelnet::Receive()
|
|
{
|
|
OvmsConsole::Event event;
|
|
event.type = OvmsConsole::event_type_t::RECV;
|
|
event.mbuf = &m_connection->recv_mbuf;
|
|
BaseType_t ret = xQueueSendToBack(m_queue, (void * )&event, (portTickType)(1000 / portTICK_PERIOD_MS));
|
|
if (ret == pdPASS)
|
|
{
|
|
// Process this input in sequence with any queued logging.
|
|
Poll(0);
|
|
}
|
|
else
|
|
ESP_LOGE(tag, "Timeout queueing message in ConsoleTelnet::Receive\n");
|
|
mbuf_remove(event.mbuf, event.mbuf->len);
|
|
}
|
|
|
|
void ConsoleTelnet::HandleDeviceEvent(void* pEvent)
|
|
{
|
|
Event event = *(Event*)pEvent;
|
|
switch (event.type)
|
|
{
|
|
case RECV:
|
|
telnet_recv(m_telnet, event.mbuf->buf, event.mbuf->len);
|
|
break;
|
|
|
|
default:
|
|
ESP_LOGE(tag, "Unknown event type in ConsoleTelnet");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void ConsoleTelnet::TelnetCallback(telnet_t *telnet, telnet_event_t *event, void *userData)
|
|
{
|
|
((ConsoleTelnet*)userData)->TelnetHandler(event);
|
|
}
|
|
|
|
void ConsoleTelnet::TelnetHandler(telnet_event_t *event)
|
|
{
|
|
switch (event->type)
|
|
{
|
|
case TELNET_EV_SEND:
|
|
mg_send(m_connection, event->data.buffer, event->data.size);
|
|
break;
|
|
|
|
case TELNET_EV_DATA:
|
|
{
|
|
char *buffer = (char *)event->data.buffer;
|
|
size_t size = (size_t)event->data.size;
|
|
// Translate CR (Enter) from telnet client into \n for microrl
|
|
for (size_t i = 0; i < size; ++i)
|
|
{
|
|
if (buffer[i] == '\r')
|
|
buffer[i] = '\n';
|
|
}
|
|
ProcessChars(buffer, size);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
#ifdef LOGEVENTS
|
|
ESP_LOGI(tag, "telnet event: %s", eventToString(event->type));
|
|
#endif
|
|
break;
|
|
}
|
|
}
|
|
|
|
// This is called to shut down the Telnet connection when the "exit" command is input.
|
|
void ConsoleTelnet::Exit()
|
|
{
|
|
printf("logout\n");
|
|
m_connection->flags |= MG_F_SEND_AND_CLOSE;
|
|
}
|
|
|
|
int ConsoleTelnet::puts(const char* s)
|
|
{
|
|
if (!m_telnet)
|
|
return -1;
|
|
telnet_send_text(m_telnet, s, strlen(s));
|
|
telnet_send_text(m_telnet, &newline, 1);
|
|
return 0;
|
|
}
|
|
|
|
int ConsoleTelnet::printf(const char* fmt, ...)
|
|
{
|
|
if (!m_telnet)
|
|
return 0;
|
|
va_list args;
|
|
va_start(args,fmt);
|
|
int ret = telnet_vprintf(m_telnet, fmt, args);
|
|
va_end(args);
|
|
return ret;
|
|
}
|
|
|
|
ssize_t ConsoleTelnet::write(const void *buf, size_t nbyte)
|
|
{
|
|
if (!m_telnet || (m_connection->flags & MG_F_SEND_AND_CLOSE))
|
|
return 0;
|
|
telnet_send_text(m_telnet, (const char*)buf, nbyte);
|
|
return nbyte;
|
|
}
|
|
|
|
/**
|
|
* Convert a telnet event type to its string representation.
|
|
*/
|
|
#ifdef LOGEVENTS
|
|
static const char *eventToString(telnet_event_type_t type) {
|
|
switch(type) {
|
|
case TELNET_EV_COMPRESS:
|
|
return "TELNET_EV_COMPRESS";
|
|
case TELNET_EV_DATA:
|
|
return "TELNET_EV_DATA";
|
|
case TELNET_EV_DO:
|
|
return "TELNET_EV_DO";
|
|
case TELNET_EV_DONT:
|
|
return "TELNET_EV_DONT";
|
|
case TELNET_EV_ENVIRON:
|
|
return "TELNET_EV_ENVIRON";
|
|
case TELNET_EV_ERROR:
|
|
return "TELNET_EV_ERROR";
|
|
case TELNET_EV_IAC:
|
|
return "TELNET_EV_IAC";
|
|
case TELNET_EV_MSSP:
|
|
return "TELNET_EV_MSSP";
|
|
case TELNET_EV_SEND:
|
|
return "TELNET_EV_SEND";
|
|
case TELNET_EV_SUBNEGOTIATION:
|
|
return "TELNET_EV_SUBNEGOTIATION";
|
|
case TELNET_EV_TTYPE:
|
|
return "TELNET_EV_TTYPE";
|
|
case TELNET_EV_WARNING:
|
|
return "TELNET_EV_WARNING";
|
|
case TELNET_EV_WILL:
|
|
return "TELNET_EV_WILL";
|
|
case TELNET_EV_WONT:
|
|
return "TELNET_EV_WONT";
|
|
case TELNET_EV_ZMP:
|
|
return "TELNET_EV_ZMP";
|
|
}
|
|
return "Unknown type";
|
|
} // eventToString
|
|
#endif
|