FreeRDP-WebConnect WebSockets gateway  1.0.0.167
 All Classes Namespaces Functions Variables Typedefs Enumerations Friends Pages
wsendpoint.hpp
1 /* vim: set et ts=4 sw=4 cindent:
2  *
3  * FreeRDP-WebConnect,
4  * A gateway for seamless access to your RDP-Sessions in any HTML5-compliant browser.
5  *
6  * Copyright 2012 Fritz Elfert <wsgate@fritz-elfert.de>
7  * This file has been partially derived from the WebSockets++ project at
8  * https://github.com/zaphoyd/websocketpp which is licensed under a BSD-license.
9  *
10  * Licensed under the Apache License, Version 2.0 (the "License");
11  * you may not use this file except in compliance with the License.
12  * You may obtain a copy of the License at
13  *
14  * http://www.apache.org/licenses/LICENSE-2.0
15  *
16  * Unless required by applicable law or agreed to in writing, software
17  * distributed under the License is distributed on an "AS IS" BASIS,
18  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19  * See the License for the specific language governing permissions and
20  * limitations under the License.
21  */
22 
23 #ifndef WSENDPOINT_H
24 #define WSENDPOINT_H
25 
26 #include <vector>
27 #include <sstream>
28 #include <iostream>
29 #include <string>
30 #include <boost/thread.hpp>
31 #include "wsframe.hpp"
32 #include "wsgate.hpp"
33 #include "wshandler.hpp"
34 
35 #ifndef HAVE_BOOST_LOCK_GUARD
36 #include <pthread.h>
37 
41 class MutexHelper {
42  public:
49  MutexHelper(pthread_mutex_t *mutex, bool locknow = true) :
50  m_pMutex(mutex), m_bLocked(false)
51  {
52  if (locknow)
53  Lock();
54  }
55 
59  ~MutexHelper()
60  {
61  if (m_bLocked)
62  pthread_mutex_unlock(m_pMutex);
63  }
64 
68  void Lock()
69  {
70  pthread_mutex_lock(m_pMutex);
71  m_bLocked = true;
72  }
73 
77  void Unlock()
78  {
79  m_bLocked = false;
80  pthread_mutex_unlock(m_pMutex);
81  }
82  private:
83  pthread_mutex_t *m_pMutex;
84  bool m_bLocked;
85 
86  MutexHelper(const MutexHelper &);
87  MutexHelper & operator=(const MutexHelper &);
88 };
89 #endif
90 
91 namespace wspp {
92 
93  using wsgate::log;
94 
98  class wsendpoint {
99  private:
100  // Non-copyable
102  wsendpoint& operator=(const wspp::wsendpoint&);
103 
104  public:
109  wsendpoint(wshandler *h)
110  : m_rng(simple_rng())
111  , m_parser(frame::parser<simple_rng>(m_rng))
112  , m_state(session::state::OPEN)
113  , m_lock()
114  , m_handler(h)
115  {
116 #ifndef HAVE_BOOST_LOCK_GUARD
117  pthread_mutexattr_t mattr;
118  pthread_mutexattr_init(&mattr);
119  pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
120  pthread_mutex_init(&m_lock, &mattr);
121  pthread_mutexattr_destroy(&mattr);
122 #endif
123  m_handler->m_endpoint = this;
124  }
125 
126 #ifndef HAVE_BOOST_LOCK_GUARD
127  ~wsendpoint() { pthread_mutex_destroy(&m_lock); }
128 #endif
129 
141  void AddRxData(std::string data)
142  {
143  std::istringstream s(data);
144  while (m_state != session::state::CLOSED && s.rdbuf()->in_avail()) {
145  try {
146  m_parser.consume(s);
147  if (m_parser.ready()) {
148  if (m_parser.is_control()) {
149  process_control();
150  } else {
151  process_data();
152  }
153  m_parser.reset();
154  }
155  } catch (const tracing::wserror & e) {
156  if (m_parser.ready()) {
157  m_parser.reset();
158  }
159  switch(e.code()) {
160  case tracing::wserror::PROTOCOL_VIOLATION:
161  send_close(close::status::PROTOCOL_ERROR,e.what());
162  break;
163  case tracing::wserror::PAYLOAD_VIOLATION:
164  send_close(close::status::INVALID_PAYLOAD,e.what());
165  break;
166  case tracing::wserror::INTERNAL_ENDPOINT_ERROR:
167  send_close(close::status::INTERNAL_ENDPOINT_ERROR,e.what());
168  break;
169  case tracing::wserror::SOFT_ERROR:
170  continue;
171  case tracing::wserror::MESSAGE_TOO_BIG:
172  send_close(close::status::MESSAGE_TOO_BIG,e.what());
173  break;
174  case tracing::wserror::OUT_OF_MESSAGES:
175  // we need to wait for a message to be returned by the
176  // client. We exit the read loop. handle_read_frame
177  // will be restarted by recycle()
178  //m_read_state = WAITING;
179  //m_endpoint.wait(type::shared_from_this());
180  return;
181  default:
182  // Fatal error, forcibly end connection immediately.
183  log::warn
184  << "Dropping TCP due to unrecoverable exception: " << e.code()
185  << " (" << e.what() << ")" << std::endl;
186  shutdown();
187  }
188  break;
189  }
190  }
191  }
192 
200  void send(const std::string& payload, frame::opcode::value op) {
201 #ifdef HAVE_BOOST_LOCK_GUARD
202  boost::lock_guard<boost::recursive_mutex> lock(m_lock);
203 #else
204  MutexHelper((pthread_mutex_t *)&m_lock);
205 #endif
206 
207  if (m_state != session::state::OPEN) {
208  log::err << "send: enpoint-state not OPEN";
209  return;
210  }
211  frame::parser<simple_rng> control(m_rng);
212  control.set_opcode(op);
213  control.set_fin(true);
214  control.set_masked(false);
215  control.set_payload(payload);
216 
217  std::string tmp(control.get_header_str());
218  tmp.append(control.get_payload_str());
219  m_handler->do_response(tmp);
220  }
221 
222  private:
223  void process_data() {
224  m_handler->on_message(m_parser.get_header_str(), m_parser.get_payload_str());
225  }
226 
228 
238  void shutdown() {
239 #ifdef HAVE_BOOST_LOCK_GUARD
240  boost::lock_guard<boost::recursive_mutex> lock(m_lock);
241 #else
242  MutexHelper((pthread_mutex_t *)&m_lock);
243 #endif
244 
245  if (m_state == session::state::CLOSED) {return;}
246 
247  m_state = session::state::CLOSED;
248  m_handler->on_close();
249  }
250 
263  void pong(const std::vector<unsigned char> & payload) {
264 #ifdef HAVE_BOOST_LOCK_GUARD
265  boost::lock_guard<boost::recursive_mutex> lock(m_lock);
266 #else
267  MutexHelper((pthread_mutex_t *)&m_lock);
268 #endif
269 
270  if (m_state != session::state::OPEN) {return;}
271 
272  // TODO: optimize control messages and handle case where
273  // endpoint is out of messages
274  frame::parser<simple_rng> control(m_rng);
275  control.set_opcode(frame::opcode::PONG);
276  control.set_fin(true);
277  control.set_masked(false);
278  control.set_payload(payload);
279 
280  std::string tmp(control.get_header_str());
281  tmp.append(control.get_payload_str());
282  m_handler->do_response(tmp);
283  }
284 
286 
297  void send_close(close::status::value code, const std::string& reason) {
298 #ifdef HAVE_BOOST_LOCK_GUARD
299  boost::lock_guard<boost::recursive_mutex> lock(m_lock);
300 #else
301  MutexHelper((pthread_mutex_t *)&m_lock);
302 #endif
303 
304  if (m_state != session::state::OPEN) {
305  log::err << "Tried to disconnect a session that wasn't open" << std::endl;
306  return;
307  }
308 
309  if (close::status::invalid(code)) {
310  log::err << "Tried to close a connection with invalid close code: "
311  << code << std::endl;
312  return;
313  } else if (close::status::reserved(code)) {
314  log::err << "Tried to close a connection with reserved close code: "
315  << code << std::endl;
316  return;
317  }
318 
319  m_state = session::state::CLOSING;
320 
321  frame::parser<simple_rng> control(m_rng);
322  control.set_opcode(frame::opcode::CLOSE);
323  control.set_fin(true);
324  control.set_masked(false);
325  if (code != close::status::NO_STATUS) {
326  const uint16_t payload = htons(code);
327  std::string pl(reinterpret_cast<const char*>(&payload), 2);
328  pl.append(reason);
329  control.set_payload(pl);
330  }
331 
332  std::string tmp(control.get_header_str());
333  tmp.append(control.get_payload_str());
334  m_handler->do_response(tmp);
335  }
336 
338 
343  void send_close_ack(close::status::value remote_close_code, std::string remote_close_reason) {
344  close::status::value local_close_code;
345  std::string local_close_reason;
346  // echo close value unless there is a good reason not to.
347  if (remote_close_code == close::status::NO_STATUS) {
348  local_close_code = close::status::NORMAL;
349  local_close_reason = "";
350  } else if (remote_close_code == close::status::ABNORMAL_CLOSE) {
351  // TODO: can we possibly get here? This means send_close_ack was
352  // called after a connection ended without getting a close
353  // frame
354  throw "shouldn't be here";
355  } else if (close::status::invalid(remote_close_code)) {
356  // TODO: shouldn't be able to get here now either
357  local_close_code = close::status::PROTOCOL_ERROR;
358  local_close_reason = "Status code is invalid";
359  } else if (close::status::reserved(remote_close_code)) {
360  // TODO: shouldn't be able to get here now either
361  local_close_code = close::status::PROTOCOL_ERROR;
362  local_close_reason = "Status code is reserved";
363  } else {
364  local_close_code = remote_close_code;
365  local_close_reason = remote_close_reason;
366  }
367 
368  // TODO: check whether we should cancel the current in flight write.
369  // if not canceled the close message will be sent as soon as the
370  // current write completes.
371 
372 
373  frame::parser<simple_rng> control(m_rng);
374  control.set_opcode(frame::opcode::CLOSE);
375  control.set_fin(true);
376  control.set_masked(false);
377  if (local_close_code != close::status::NO_STATUS) {
378  const uint16_t payload = htons(local_close_code);
379  std::string pl(reinterpret_cast<const char*>(&payload), 2);
380  pl.append(local_close_reason);
381  control.set_payload(pl);
382  }
383 
384  std::string tmp(control.get_header_str());
385  tmp.append(control.get_payload_str());
386  m_handler->do_response(tmp);
387  shutdown();
388  }
389 
390  void process_control() {
391  switch (m_parser.get_opcode()) {
392  case frame::opcode::PING:
393  if (m_handler->on_ping(m_parser.get_payload_str())) {
394  pong(m_parser.get_payload());
395  }
396  break;
397  case frame::opcode::PONG:
398  m_handler->on_pong(m_parser.get_payload_str());
399  break;
400  case frame::opcode::CLOSE:
401  // check that the codes we got over the wire are valid
402  if (m_state == session::state::OPEN) {
403  // other end is initiating
404  log::debug << "sending close ack" << std::endl;
405 
406  // TODO:
407  send_close_ack(m_parser.get_close_code(), m_parser.get_close_reason());
408  } else if (m_state == session::state::CLOSING) {
409  // ack of our close
410  log::debug << "got close ack" << std::endl;
411  shutdown();
412  }
413  break;
414  default:
415  throw tracing::wserror("Invalid Opcode",
416  tracing::wserror::PROTOCOL_VIOLATION);
417  break;
418  }
419  }
420 
421  private:
422  simple_rng m_rng;
423  frame::parser<simple_rng> m_parser;
424  session::state::value m_state;
425 #ifdef HAVE_BOOST_LOCK_GUARD
426  mutable boost::recursive_mutex m_lock;
427 #else
428  mutable pthread_mutex_t m_lock;
429 #endif
430  wshandler *m_handler;
431  };
432 
433  void wshandler::send_text(const std::string & data) {
434  if (m_endpoint) {
435  m_endpoint->send(data, frame::opcode::TEXT);
436  }
437  }
438  void wshandler::send_binary(const std::string & data) {
439  if (m_endpoint) {
440  m_endpoint->send(data, frame::opcode::BINARY);
441  }
442  }
443 
444 }
445 
446 #endif