xref: /openbmc/bmcweb/include/kvm_websocket.hpp (revision e278c18fc22faa2e5099f3f43a813942144c7560)
1 #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 
10 namespace crow
11 {
12 namespace obmc_kvm
13 {
14 
15 static std::unique_ptr<boost::asio::ip::tcp::socket> hostSocket;
16 
17 // TODO(ed) validate that these buffer sizes are sane
18 static boost::beast::flat_static_buffer<1024U * 50U> outputBuffer;
19 static boost::beast::flat_static_buffer<1024U> inputBuffer;
20 
21 static crow::websocket::Connection* session = nullptr;
22 
23 static bool doingWrite = false;
24 
25 inline void doWrite();
26 
27 inline void WriteDone(const boost::system::error_code& ec,
28                       std::size_t bytesWritten)
29 {
30     BMCWEB_LOG_DEBUG << "Wrote " << bytesWritten << "bytes";
31     doingWrite = false;
32     inputBuffer.consume(bytesWritten);
33 
34     if (session == nullptr)
35     {
36         return;
37     }
38     if (ec == boost::asio::error::eof)
39     {
40         session->close("KVM socket port closed");
41         return;
42     }
43     if (ec)
44     {
45         session->close("Error in reading to host port");
46         BMCWEB_LOG_ERROR << "Error in KVM socket write " << ec;
47         return;
48     }
49 
50     doWrite();
51 }
52 
53 inline void doWrite()
54 {
55     if (doingWrite)
56     {
57         BMCWEB_LOG_DEBUG << "Already writing.  Bailing out";
58         return;
59     }
60     if (inputBuffer.size() == 0)
61     {
62         BMCWEB_LOG_DEBUG << "inputBuffer empty.  Bailing out";
63         return;
64     }
65 
66     doingWrite = true;
67     hostSocket->async_write_some(inputBuffer.data(), WriteDone);
68 }
69 
70 inline void doRead();
71 
72 inline void readDone(const boost::system::error_code& ec, std::size_t bytesRead)
73 {
74     BMCWEB_LOG_DEBUG << "read done.  Read " << bytesRead << " bytes";
75     if (ec)
76     {
77         BMCWEB_LOG_ERROR << "Couldn't read from KVM socket port: " << ec;
78         if (session != nullptr)
79         {
80             session->close("Error in connecting to KVM port");
81         }
82         return;
83     }
84     if (session == nullptr)
85     {
86         return;
87     }
88 
89     outputBuffer.commit(bytesRead);
90     boost::beast::string_view payload(
91         static_cast<const char*>(outputBuffer.data().data()), bytesRead);
92     BMCWEB_LOG_DEBUG << "Sending payload size " << payload.size();
93     session->sendBinary(payload);
94     outputBuffer.consume(bytesRead);
95 
96     doRead();
97 }
98 
99 inline void doRead()
100 {
101     std::size_t bytes = outputBuffer.capacity() - outputBuffer.size();
102     BMCWEB_LOG_DEBUG << "Reading " << bytes << " from kvm socket";
103     hostSocket->async_read_some(
104         outputBuffer.prepare(outputBuffer.capacity() - outputBuffer.size()),
105         readDone);
106 }
107 
108 inline void connectHandler(const boost::system::error_code& ec)
109 {
110     if (ec)
111     {
112         BMCWEB_LOG_ERROR << "Couldn't connect to KVM socket port: " << ec;
113         if (session != nullptr)
114         {
115             session->close("Error in connecting to KVM port");
116         }
117         return;
118     }
119 
120     doRead();
121 }
122 
123 inline void requestRoutes(CrowApp& app)
124 {
125     BMCWEB_ROUTE(app, "/kvm/0")
126         .websocket()
127         .onopen([](crow::websocket::Connection& conn) {
128             BMCWEB_LOG_DEBUG << "Connection " << &conn << " opened";
129 
130             if (session != nullptr)
131             {
132                 conn.close("User already connected");
133                 return;
134             }
135 
136             session = &conn;
137             if (hostSocket == nullptr)
138             {
139                 boost::asio::ip::tcp::endpoint endpoint(
140                     boost::asio::ip::make_address("127.0.0.1"), 5900);
141 
142                 hostSocket = std::make_unique<boost::asio::ip::tcp::socket>(
143                     conn.get_io_context());
144                 hostSocket->async_connect(endpoint, connectHandler);
145             }
146         })
147         .onclose(
148             [](crow::websocket::Connection& conn, const std::string& reason) {
149                 session = nullptr;
150                 hostSocket = nullptr;
151 #if BOOST_VERSION >= 107000
152                 inputBuffer.clear();
153                 outputBuffer.clear();
154 #else
155                 inputBuffer.reset();
156                 outputBuffer.reset();
157 #endif
158             })
159         .onmessage([](crow::websocket::Connection& conn,
160                       const std::string& data, bool is_binary) {
161             if (data.length() > inputBuffer.capacity())
162             {
163                 BMCWEB_LOG_ERROR << "Buffer overrun when writing "
164                                  << data.length() << " bytes";
165                 conn.close("Buffer overrun");
166                 return;
167             }
168 
169             BMCWEB_LOG_DEBUG << "Read " << data.size()
170                              << " bytes from websocket";
171             boost::asio::buffer_copy(inputBuffer.prepare(data.size()),
172                                      boost::asio::buffer(data));
173             BMCWEB_LOG_DEBUG << "commiting " << data.size()
174                              << " bytes from websocket";
175             inputBuffer.commit(data.size());
176 
177             BMCWEB_LOG_DEBUG << "inputbuffer size " << inputBuffer.size();
178             doWrite();
179         });
180 }
181 } // namespace obmc_kvm
182 } // namespace crow
183