blob: a15004ea656c39e1af7491f518455c3e2783320a [file] [log] [blame]
Ed Tanous2daf6722018-08-23 11:27:23 -07001#pragma once
2#include <crow/app.h>
3#include <crow/websocket.h>
4#include <sys/socket.h>
5
6#include <boost/container/flat_map.hpp>
7#include <boost/container/flat_set.hpp>
8#include <webserver_common.hpp>
9
10namespace crow
11{
12namespace obmc_console
13{
14
15static std::unique_ptr<boost::asio::local::stream_protocol::socket> host_socket;
16
17static std::array<char, 4096> outputBuffer;
18static std::string inputBuffer;
19
20static boost::container::flat_set<crow::websocket::Connection*> sessions;
21
22static bool doingWrite = false;
23
24void doWrite()
25{
26 if (doingWrite)
27 {
28 BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
29 return;
30 }
31
32 if (inputBuffer.empty())
33 {
34 BMCWEB_LOG_DEBUG << "Outbuffer empty. Bailing out";
35 return;
36 }
37
38 doingWrite = true;
39 host_socket->async_write_some(
40 boost::asio::buffer(inputBuffer.data(), inputBuffer.size()),
41 [](boost::beast::error_code ec, std::size_t bytes_written) {
42 doingWrite = false;
43 inputBuffer.erase(0, bytes_written);
44
45 if (ec == boost::asio::error::eof)
46 {
47 for (auto session : sessions)
48 {
49 session->close("Error in reading to host port");
50 }
51 return;
52 }
53 if (ec)
54 {
55 BMCWEB_LOG_ERROR << "Error in host serial write " << ec;
56 return;
57 }
58 doWrite();
59 });
60}
61
62void doRead()
63{
64 BMCWEB_LOG_DEBUG << "Reading from socket";
65 host_socket->async_read_some(
66 boost::asio::buffer(outputBuffer.data(), outputBuffer.size()),
67 [](const boost::system::error_code& ec, std::size_t bytesRead) {
68 BMCWEB_LOG_DEBUG << "read done. Read " << bytesRead << " bytes";
69 if (ec)
70 {
71 BMCWEB_LOG_ERROR << "Couldn't read from host serial port: "
72 << ec;
73 for (auto session : sessions)
74 {
75 session->close("Error in connecting to host port");
76 }
77 return;
78 }
79 boost::beast::string_view payload(outputBuffer.data(), bytesRead);
80 for (auto session : sessions)
81 {
82 session->sendText(payload);
83 }
84 doRead();
85 });
86}
87
88void connectHandler(const boost::system::error_code& ec)
89{
90 if (ec)
91 {
92 BMCWEB_LOG_ERROR << "Couldn't connect to host serial port: " << ec;
93 for (auto session : sessions)
94 {
95 session->close("Error in connecting to host port");
96 }
97 return;
98 }
99
100 doWrite();
101 doRead();
102}
103
104void requestRoutes(CrowApp& app)
105{
106 BMCWEB_ROUTE(app, "/console0")
107 .websocket()
108 .onopen([](crow::websocket::Connection& conn) {
109 BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
110
111 sessions.insert(&conn);
112 if (host_socket == nullptr)
113 {
114 const std::string consoleName("\0obmc-console", 13);
115 boost::asio::local::stream_protocol::endpoint ep(consoleName);
116
117 // This is a hack. For whatever reason boost local endpoint has
118 // a check to see if a string is null terminated, and if it is,
119 // it drops the path character count by 1. For abstract
120 // sockets, we need the count to be the full sizeof(s->sun_path)
121 // (ie 108), even though our path _looks_ like it's null
122 // terminated. This is likely a bug in asio that needs to be
123 // submitted Todo(ed). so the cheat here is to break the
124 // abstraction for a minute, write a 1 to the last byte, this
125 // causes the check at the end of resize here:
126 // https://www.boost.org/doc/libs/1_68_0/boost/asio/local/detail/impl/endpoint.ipp
127 // to not decrement us unesssesarily.
128 struct sockaddr_un* s =
129 reinterpret_cast<sockaddr_un*>(ep.data());
130 s->sun_path[sizeof(s->sun_path) - 1] = 1;
131 ep.resize(sizeof(sockaddr_un));
132 s->sun_path[sizeof(s->sun_path) - 1] = 0;
133
134 host_socket = std::make_unique<
135 boost::asio::local::stream_protocol::socket>(
136 conn.getIoService());
137 host_socket->async_connect(ep, connectHandler);
138 }
139 })
140 .onclose(
141 [](crow::websocket::Connection& conn, const std::string& reason) {
142 sessions.erase(&conn);
143 if (sessions.empty())
144 {
145 host_socket = nullptr;
146 inputBuffer.clear();
147 inputBuffer.shrink_to_fit();
148 }
149 })
150 .onmessage([](crow::websocket::Connection& conn,
151 const std::string& data, bool is_binary) {
152 inputBuffer += data;
153 doWrite();
154 });
155}
156} // namespace obmc_console
157} // namespace crow