1 /*
2 * Copyright (c) 2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "net_websocket_exec.h"
17
18 #include <atomic>
19 #include <memory>
20 #include <queue>
21 #include <thread>
22 #include <unistd.h>
23
24 #include "libwebsockets.h"
25 #include "net_websocket_base_context.h"
26 #include "net_websocket_impl.h"
27 #include "securec.h"
28 #include "net_websocket_utils.h"
29 #include "netstack_common_utils.h"
30 #include "netstack_log.h"
31
32 #ifdef HAS_NETMANAGER_BASE
33 #include "http_proxy.h"
34 #include "net_conn_client.h"
35 #include "network_security_config.h"
36 #endif
37
38 static constexpr const char *PROTOCOL_DELIMITER = "//";
39 static constexpr const char *NAME_END = ":";
40 static constexpr const char *STATUS_LINE_SEP = " ";
41 static constexpr const size_t STATUS_LINE_ELEM_NUM = 2;
42 static constexpr const char *PREFIX_HTTPS = "https";
43 static constexpr const char *PREFIX_WSS = "wss";
44 static constexpr const char *PREFIX_WS = "ws";
45 static constexpr const int MAX_URI_LENGTH = 1024;
46 static constexpr const int MAX_HDR_LENGTH = 1024;
47 static constexpr const int MAX_PROTOCOL_LENGTH = 1024;
48 static constexpr const int MAX_ADDRESS_LENGTH = 1024;
49 static constexpr const int FD_LIMIT_PER_THREAD = 1 + 1 + 1;
50 static constexpr const int COMMON_ERROR_CODE = 200;
51 static constexpr const char *LINK_DOWN = "The link is down";
52 static constexpr const int32_t UID_TRANSFORM_DIVISOR = 200000;
53 static constexpr const char *BASE_PATH = "/data/certificates/user_cacerts/";
54 static constexpr const char *WEBSOCKET_SYSTEM_PREPARE_CA_PATH = "/etc/security/certificates";
55 static constexpr const int FUNCTION_PARAM_TWO = 2;
56 static constexpr const char *WEBSOCKET_CLIENT_THREAD_RUN = "OS_NET_WSJsCli";
57
58 static const std::vector<std::string> WS_PREFIX = {PREFIX_WSS, PREFIX_WS};
59
60 namespace OHOS::NetStack::NetWebSocket {
61 static const lws_protocols WEBSOCKET_PROTOCOLS[] = {
62 {"lws-minimal-client-cj", NetWebSocketExec::LwsCallback, 0, 0},
63 {nullptr, nullptr, 0, 0}, // this line is needed
64 };
65
66 static const lws_retry_bo_t RETRY = {
67 .secs_since_valid_ping = 30, /* force PINGs after secs idle */
68 .secs_since_valid_hangup = 60, /* hangup after secs idle */
69 .jitter_percent = 20,
70 };
71
72 struct CallbackDispatcher {
73 lws_callback_reasons reason;
74 int (*callback)(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len);
75 };
76
77
78 class WebSocketContext {
79 public:
80 struct SendData {
SendDataOHOS::NetStack::NetWebSocket::WebSocketContext::SendData81 SendData(void *paraData, size_t paraLength, lws_write_protocol paraProtocol)
82 : data(paraData), length(paraLength), protocol(paraProtocol)
83 {
84 }
85
86 SendData() = delete;
87
88 ~SendData() = default;
89
90 void *data;
91 size_t length;
92 lws_write_protocol protocol;
93 };
94
WebSocketContext(lws_context * context)95 explicit WebSocketContext(lws_context *context)
96 : closeStatus(LWS_CLOSE_STATUS_NOSTATUS), openStatus(0), closed_(false), threadStop_(false), context_(context)
97 {
98 }
99
IsClosed()100 bool IsClosed()
101 {
102 std::lock_guard<std::mutex> lock(mutex_);
103 return closed_;
104 }
105
IsThreadStop()106 bool IsThreadStop()
107 {
108 return threadStop_.load();
109 }
110
SetThreadStop(bool threadStop)111 void SetThreadStop(bool threadStop)
112 {
113 threadStop_.store(threadStop);
114 }
115
Close(lws_close_status status,const std::string & reason)116 void Close(lws_close_status status, const std::string &reason)
117 {
118 std::lock_guard<std::mutex> lock(mutex_);
119 closeStatus = status;
120 closeReason = reason;
121 closed_ = true;
122 }
123
Push(void * data,size_t length,lws_write_protocol protocol)124 void Push(void *data, size_t length, lws_write_protocol protocol)
125 {
126 std::lock_guard<std::mutex> lock(mutex_);
127 dataQueue_.emplace(data, length, protocol);
128 }
129
Pop()130 SendData Pop()
131 {
132 std::lock_guard<std::mutex> lock(mutex_);
133 if (dataQueue_.empty()) {
134 return {nullptr, 0, LWS_WRITE_TEXT};
135 }
136 SendData data = dataQueue_.front();
137 dataQueue_.pop();
138 return data;
139 }
140
SetContext(lws_context * context)141 void SetContext(lws_context *context)
142 {
143 context_ = context;
144 }
145
GetContext()146 lws_context *GetContext()
147 {
148 return context_;
149 }
150
IsEmpty()151 bool IsEmpty()
152 {
153 std::lock_guard<std::mutex> lock(mutex_);
154 if (dataQueue_.empty()) {
155 return true;
156 }
157 return false;
158 }
159
SetLws(lws * wsi)160 void SetLws(lws *wsi)
161 {
162 std::lock_guard<std::mutex> lock(mutexForLws_);
163 if (wsi == nullptr) {
164 NETSTACK_LOGD("set wsi nullptr");
165 }
166 wsi_ = wsi;
167 }
168
TriggerWritable()169 void TriggerWritable()
170 {
171 std::lock_guard<std::mutex> lock(mutexForLws_);
172 if (wsi_ == nullptr) {
173 NETSTACK_LOGE("wsi is nullptr, can not trigger");
174 return;
175 }
176 lws_callback_on_writable(wsi_);
177 }
178
179 std::map<std::string, std::string> header;
180
181 lws_close_status closeStatus;
182
183 std::string closeReason;
184
185 uint32_t openStatus;
186
187 std::string openMessage;
188
189 private:
190 volatile bool closed_;
191
192 std::atomic_bool threadStop_;
193
194 std::mutex mutex_;
195
196 std::mutex mutexForLws_;
197
198 lws_context *context_;
199
200 std::queue<SendData> dataQueue_;
201
202 lws *wsi_ = nullptr;
203 };
204
CreateOpenPara(uint32_t status,const std::string & message)205 static uint8_t* CreateOpenPara(uint32_t status, const std::string &message)
206 {
207 COpenResponse* res = new COpenResponse;
208 res->status = status;
209 res->message = MallocCString(message);
210 return reinterpret_cast<uint8_t*>(res);
211 }
212
CreateClosePara(uint32_t code,const std::string & reason)213 static uint8_t* CreateClosePara(uint32_t code, const std::string &reason)
214 {
215 CCloseResponse* res = new CCloseResponse;
216 res->code = code;
217 res->reason = MallocCString(reason);
218 return reinterpret_cast<uint8_t*>(res);
219 }
220
CreateMessagePara(CJWebsocketProxy * websocketProxy,bool isBinary)221 static uint8_t* CreateMessagePara(CJWebsocketProxy *websocketProxy, bool isBinary)
222 {
223 CMessageResponse* res = new CMessageResponse;
224 res->resultType = isBinary ? ARRAY_BUFFER : STRING;
225 auto msg = reinterpret_cast<std::string *>(websocketProxy->GetQueueData());
226 if (!msg) {
227 NETSTACK_LOGE("msg is nullptr");
228 delete res;
229 return nullptr;
230 }
231 CArrUI8 body;
232 body.head = reinterpret_cast<uint8_t*>(MallocCString(*msg));
233 body.size = static_cast<int64_t>(msg->size());
234 res->result = body;
235 return reinterpret_cast<uint8_t*>(res);
236 }
237
CreateResponseHeader(const std::map<std::string,std::string> & headers)238 static uint8_t* CreateResponseHeader(const std::map<std::string, std::string> &headers)
239 {
240 CReceiveResponse* res = new CReceiveResponse;
241 if (headers.empty()) {
242 res->headerType = UNDEFINED;
243 res->header.head = nullptr;
244 res->header.size = 0;
245 }
246 res->headerType = MAP;
247 res->header = Map2CArrString(headers);
248 return reinterpret_cast<uint8_t*>(res);
249 }
250
CreateError(int32_t code,uint32_t httpResponse)251 static uint8_t* CreateError(int32_t code, uint32_t httpResponse)
252 {
253 CErrorResponse* res = new CErrorResponse;
254 res->code = code;
255 res->httpResponse = httpResponse;
256 return reinterpret_cast<uint8_t*>(res);
257 }
258
OnConnectError(CJWebsocketProxy * websocketProxy,int32_t code,uint32_t httpResponse)259 void OnConnectError(CJWebsocketProxy *websocketProxy, int32_t code, uint32_t httpResponse)
260 {
261 NETSTACK_LOGI("OnConnectError code is %{public}d, httpResponse is %{public}d", code, httpResponse);
262 if (websocketProxy == nullptr) {
263 NETSTACK_LOGE("websocketProxy is null");
264 return;
265 }
266 if (auto webSocketContext = websocketProxy->GetWebSocketContext(); webSocketContext != nullptr) {
267 NETSTACK_LOGI("OnConnectError SetThreadStop");
268 webSocketContext->SetThreadStop(true);
269 }
270 if (websocketProxy->FindCallback(EVENT_OPEN) == std::nullopt) {
271 NETSTACK_LOGI("no event listener: ERROR");
272 return;
273 }
274 CWebSocketCallbackData* para = new CWebSocketCallbackData;
275 para->code = ERR_OK;
276 para->typeId = EVENT_ERROR;
277 para->data = CreateError(code, httpResponse);
278 para->dataLen = sizeof(CErrorResponse);
279 websocketProxy->EmitCallBack(para);
280 delete reinterpret_cast<CErrorResponse*>(para->data);
281 delete para;
282 }
283
CreatConnectInfo(WebSocketConnectContext * context,lws_context * lwsContext,CJWebsocketProxy * websocketProxy)284 bool NetWebSocketExec::CreatConnectInfo(WebSocketConnectContext *context,
285 lws_context *lwsContext, CJWebsocketProxy *websocketProxy)
286 {
287 lws_client_connect_info connectInfo = {};
288 char protocol[MAX_URI_LENGTH] = {0};
289 char address[MAX_URI_LENGTH] = {0};
290 char path[MAX_URI_LENGTH] = {0};
291 char customizedProtocol[MAX_PROTOCOL_LENGTH] = {0};
292 int port = 0;
293
294 if (!ParseUrl(context, protocol, MAX_URI_LENGTH, address, MAX_URI_LENGTH, path, MAX_URI_LENGTH, &port)) {
295 NETSTACK_LOGE("ParseUrl failed");
296 context->SetErrorCode(WEBSOCKET_ERROR_CODE_URL_ERROR);
297 return false;
298 }
299 if (lwsContext == nullptr) {
300 NETSTACK_LOGE("no memory");
301 return false;
302 }
303 std::string tempHost = std::string(address) + NAME_END + std::to_string(port);
304 std::string tempOrigin = std::string(protocol) + NAME_END + PROTOCOL_DELIMITER + tempHost;
305 NETSTACK_LOGD("tempOrigin = %{private}s", tempOrigin.c_str());
306 if (strcpy_s(customizedProtocol, context->GetProtocol().length() + 1, context->GetProtocol().c_str()) != ERR_OK) {
307 NETSTACK_LOGE("memory copy failed");
308 }
309
310 connectInfo.context = lwsContext;
311 connectInfo.port = port;
312 connectInfo.address = address;
313 connectInfo.path = path;
314 connectInfo.host = address;
315 connectInfo.origin = address;
316 connectInfo.protocol = customizedProtocol;
317
318 if (strcmp(protocol, PREFIX_HTTPS) == 0 || strcmp(protocol, PREFIX_WSS) == 0) {
319 connectInfo.ssl_connection = LCCSCF_USE_SSL | LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK | LCCSCF_ALLOW_SELFSIGNED;
320 }
321 lws *wsi = nullptr;
322 connectInfo.pwsi = &wsi;
323 connectInfo.retry_and_idle_policy = &RETRY;
324 connectInfo.userdata = websocketProxy;
325 if (lws_client_connect_via_info(&connectInfo) == nullptr) {
326 NETSTACK_LOGI("ExecConnect websocket connect failed");
327 context->SetErrorCode(-1);
328 OnConnectError(websocketProxy, COMMON_ERROR_CODE, 0);
329 return false;
330 }
331 return true;
332 }
333
RunService(std::shared_ptr<WebSocketContext> webSocketContext,CJWebsocketProxy * websocketProxy)334 void RunService(std::shared_ptr<WebSocketContext> webSocketContext, CJWebsocketProxy* websocketProxy)
335 {
336 NETSTACK_LOGI("websocket run service start");
337 int res = 0;
338 lws_context *context = webSocketContext->GetContext();
339 if (context == nullptr) {
340 NETSTACK_LOGE("context is null");
341 return;
342 }
343 while (res >= 0 && !webSocketContext->IsThreadStop()) {
344 res = lws_service(context, 0);
345 }
346 lws_context_destroy(context);
347 webSocketContext->SetContext(nullptr);
348 websocketProxy->SetWebSocketContext(nullptr);
349 NETSTACK_LOGI("websocket run service end");
350 }
351
352
ExecConnect(WebSocketConnectContext * context)353 bool NetWebSocketExec::ExecConnect(WebSocketConnectContext *context)
354 {
355 NETSTACK_LOGD("websocket Connect exec");
356 if (context == nullptr) {
357 NETSTACK_LOGE("context is nullptr");
358 return false;
359 }
360 if (!CommonUtils::HasInternetPermission()) {
361 context->SetPermissionDenied(true);
362 return false;
363 }
364
365 auto websocketProxy = context->GetWebsocketProxy();
366 if (websocketProxy == nullptr) {
367 return false;
368 }
369 lws_context_creation_info info = {};
370 char proxyAds[MAX_ADDRESS_LENGTH] = {0};
371 FillContextInfo(context, info, proxyAds);
372 if (!FillCaPath(context, info)) {
373 return false;
374 }
375 lws_context *lwsContext = nullptr;
376 std::shared_ptr<WebSocketContext> webSocketContext;
377 if (websocketProxy->GetWebSocketContext() == nullptr) {
378 lwsContext = lws_create_context(&info);
379 webSocketContext = std::make_shared<WebSocketContext>(lwsContext);
380 webSocketContext->header = context->header;
381 websocketProxy->SetWebSocketContext(webSocketContext);
382 } else {
383 NETSTACK_LOGE("Websocket connect already exist");
384 context->SetErrorCode(WEBSOCKET_ERROR_CODE_CONNECT_AlREADY_EXIST);
385 return false;
386 }
387 if (!CreatConnectInfo(context, lwsContext, websocketProxy)) {
388 webSocketContext->SetContext(nullptr);
389 lws_context_destroy(lwsContext);
390 websocketProxy->SetWebSocketContext(nullptr);
391 return false;
392 }
393 std::thread serviceThread(RunService, webSocketContext, websocketProxy);
394
395 #if defined(MAC_PLATFORM) || defined(IOS_PLATFORM)
396 pthread_setname_np(WEBSOCKET_CLIENT_THREAD_RUN);
397 #else
398 pthread_setname_np(serviceThread.native_handle(), WEBSOCKET_CLIENT_THREAD_RUN);
399 #endif
400 serviceThread.detach();
401 return true;
402 }
403
ExecSend(WebSocketSendContext * context)404 bool NetWebSocketExec::ExecSend(WebSocketSendContext *context)
405 {
406 if (context == nullptr) {
407 NETSTACK_LOGE("context is nullptr");
408 return false;
409 }
410 if (!CommonUtils::HasInternetPermission()) {
411 context->SetPermissionDenied(true);
412 return false;
413 }
414 auto websocketProxy = context->GetWebsocketProxy();
415 if (websocketProxy == nullptr) {
416 NETSTACK_LOGE("context is null");
417 return false;
418 }
419 auto webSocketContext = websocketProxy->GetWebSocketContext();
420 if (webSocketContext == nullptr) {
421 NETSTACK_LOGE("user data is nullptr");
422 return false;
423 }
424 if (webSocketContext->IsClosed() || webSocketContext->IsThreadStop()) {
425 NETSTACK_LOGE("session is closed or stopped");
426 return false;
427 }
428 webSocketContext->Push(context->data, context->length, context->protocol);
429 webSocketContext->TriggerWritable();
430 NETSTACK_LOGD("lws ts send success");
431 return true;
432 }
433
ExecClose(WebSocketCloseContext * context)434 bool NetWebSocketExec::ExecClose(WebSocketCloseContext *context)
435 {
436 if (context == nullptr) {
437 NETSTACK_LOGE("context is nullptr");
438 return false;
439 }
440 if (!CommonUtils::HasInternetPermission()) {
441 context->SetPermissionDenied(true);
442 return false;
443 }
444 if (context->GetWebsocketProxy() == nullptr) {
445 NETSTACK_LOGE("context is null");
446 return false;
447 }
448
449 auto websocketProxy = context->GetWebsocketProxy();
450 auto webSocketContext = websocketProxy->GetWebSocketContext();
451 if (webSocketContext == nullptr) {
452 NETSTACK_LOGE("user data is nullptr");
453 return false;
454 }
455
456 if (webSocketContext->IsClosed()) {
457 NETSTACK_LOGE("connection has been closed");
458 return false;
459 }
460 webSocketContext->Close(static_cast<lws_close_status>(context->code), context->reason);
461 webSocketContext->TriggerWritable();
462 NETSTACK_LOGI("ExecClose OK");
463 return true;
464 }
465
ParseUrl(WebSocketConnectContext * context,char * protocol,size_t protocolLen,char * address,size_t addressLen,char * path,size_t pathLen,int * port)466 bool NetWebSocketExec::ParseUrl(WebSocketConnectContext *context, char *protocol, size_t protocolLen, char *address,
467 size_t addressLen, char *path, size_t pathLen, int *port)
468 {
469 char uri[MAX_URI_LENGTH] = {0};
470 if (strcpy_s(uri, MAX_URI_LENGTH, context->url.c_str()) < 0) {
471 NETSTACK_LOGE("strcpy_s failed");
472 return false;
473 }
474 const char *tempProt = nullptr;
475 const char *tempAddress = nullptr;
476 const char *tempPath = nullptr;
477 (void)lws_parse_uri(uri, &tempProt, &tempAddress, port, &tempPath);
478 if (strcpy_s(protocol, protocolLen, tempProt) < 0) {
479 NETSTACK_LOGE("strcpy_s failed");
480 return false;
481 }
482 if (std::find(WS_PREFIX.begin(), WS_PREFIX.end(), protocol) == WS_PREFIX.end()) {
483 NETSTACK_LOGE("protocol failed");
484 return false;
485 }
486 if (strcpy_s(address, addressLen, tempAddress) < 0) {
487 NETSTACK_LOGE("strcpy_s failed");
488 return false;
489 }
490 if (strcpy_s(path, pathLen, tempPath) < 0) {
491 NETSTACK_LOGE("strcpy_s failed");
492 return false;
493 }
494 return true;
495 }
496
RaiseError(CJWebsocketProxy * websocketProxy,uint32_t httpResponse)497 int NetWebSocketExec::RaiseError(CJWebsocketProxy *websocketProxy, uint32_t httpResponse)
498 {
499 OnError(websocketProxy, COMMON_ERROR_CODE, httpResponse);
500 return -1;
501 }
502
LwsCallback(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)503 int NetWebSocketExec::LwsCallback(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
504 {
505 NETSTACK_LOGI("lws callback reason is %{public}d", reason);
506 CallbackDispatcher dispatchers[] = {
507 {LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, LwsCallbackClientAppendHandshakeHeader},
508 {LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, LwsCallbackWsPeerInitiatedClose},
509 {LWS_CALLBACK_CLIENT_WRITEABLE, LwsCallbackClientWritable},
510 {LWS_CALLBACK_CLIENT_CONNECTION_ERROR, LwsCallbackClientConnectionError},
511 {LWS_CALLBACK_CLIENT_RECEIVE, LwsCallbackClientReceive},
512 {LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, LwsCallbackClientFilterPreEstablish},
513 {LWS_CALLBACK_CLIENT_ESTABLISHED, LwsCallbackClientEstablished},
514 {LWS_CALLBACK_CLIENT_CLOSED, LwsCallbackClientClosed},
515 {LWS_CALLBACK_WSI_DESTROY, LwsCallbackWsiDestroy},
516 {LWS_CALLBACK_PROTOCOL_DESTROY, LwsCallbackProtocolDestroy},
517 {LWS_CALLBACK_VHOST_CERT_AGING, LwsCallbackVhostCertAging},
518 };
519
520 for (const auto dispatcher : dispatchers) {
521 if (dispatcher.reason == reason) {
522 return dispatcher.callback(wsi, reason, user, in, len);
523 }
524 }
525
526 return HttpDummy(wsi, reason, user, in, len);
527 }
528
HttpDummy(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)529 int NetWebSocketExec::HttpDummy(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
530 {
531 int ret = lws_callback_http_dummy(wsi, reason, user, in, len);
532 if (ret < 0) {
533 OnError(reinterpret_cast<CJWebsocketProxy *>(user), COMMON_ERROR_CODE, GetHttpResponseFromWsi(wsi));
534 }
535 return 0;
536 }
537
LwsCallbackClientAppendHandshakeHeader(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)538 int NetWebSocketExec::LwsCallbackClientAppendHandshakeHeader(lws *wsi, lws_callback_reasons reason,
539 void *user, void *in, size_t len)
540 {
541 NETSTACK_LOGD("lws callback client append handshake header");
542 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
543 auto webSocketContext = websocketProxy->GetWebSocketContext();
544 if (webSocketContext == nullptr) {
545 NETSTACK_LOGE("user data is null");
546 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
547 }
548
549 auto payload = reinterpret_cast<unsigned char **>(in);
550 if (payload == nullptr || (*payload) == nullptr || len == 0) {
551 NETSTACK_LOGE("header payload is null, do not append header");
552 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
553 }
554 auto payloadEnd = (*payload) + len;
555 for (const auto &pair : webSocketContext->header) {
556 std::string name = pair.first + NAME_END;
557 if (lws_add_http_header_by_name(wsi, reinterpret_cast<const unsigned char *>(name.c_str()),
558 reinterpret_cast<const unsigned char *>(pair.second.c_str()),
559 static_cast<int>(strlen(pair.second.c_str())), payload, payloadEnd)) {
560 NETSTACK_LOGE("add header failed");
561 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
562 }
563 }
564 NETSTACK_LOGI("add header OK");
565 return HttpDummy(wsi, reason, user, in, len);
566 }
567
LwsCallbackWsPeerInitiatedClose(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)568 int NetWebSocketExec::LwsCallbackWsPeerInitiatedClose(lws *wsi, lws_callback_reasons reason,
569 void *user, void *in, size_t len)
570 {
571 NETSTACK_LOGD("lws callback ws peer initiated close");
572 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
573 auto webSocketContext = websocketProxy->GetWebSocketContext();
574 if (webSocketContext == nullptr) {
575 NETSTACK_LOGE("user data is null");
576 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
577 }
578
579 if (in == nullptr || len < sizeof(uint16_t)) {
580 NETSTACK_LOGI("No close reason");
581 webSocketContext->Close(LWS_CLOSE_STATUS_NORMAL, "");
582 return HttpDummy(wsi, reason, user, in, len);
583 }
584
585 uint16_t closeStatus = ntohs(*reinterpret_cast<uint16_t *>(in));
586 std::string closeReason;
587 closeReason.append(reinterpret_cast<char *>(in) + sizeof(uint16_t), len - sizeof(uint16_t));
588 webSocketContext->Close(static_cast<lws_close_status>(closeStatus), closeReason);
589 return HttpDummy(wsi, reason, user, in, len);
590 }
591
LwsCallbackClientWritable(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)592 int NetWebSocketExec::LwsCallbackClientWritable(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
593 {
594 NETSTACK_LOGD("lws callback client writable");
595 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
596 auto webSocketContext = websocketProxy->GetWebSocketContext();
597 if (webSocketContext == nullptr) {
598 NETSTACK_LOGE("user data is null");
599 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
600 }
601 if (webSocketContext->IsClosed()) {
602 NETSTACK_LOGI("need to close");
603 lws_close_reason(wsi, webSocketContext->closeStatus,
604 reinterpret_cast<unsigned char *>(const_cast<char *>(webSocketContext->closeReason.c_str())),
605 strlen(webSocketContext->closeReason.c_str()));
606 // here do not emit error, because we close it
607 return -1;
608 }
609 auto sendData = webSocketContext->Pop();
610 if (sendData.data == nullptr || sendData.length == 0) {
611 return HttpDummy(wsi, reason, user, in, len);
612 }
613 int sendLength = lws_write(wsi, reinterpret_cast<unsigned char *>(sendData.data) + LWS_SEND_BUFFER_PRE_PADDING,
614 sendData.length, sendData.protocol);
615 free(sendData.data);
616 NETSTACK_LOGD("lws send data length is %{public}d", sendLength);
617 if (!webSocketContext->IsEmpty()) {
618 lws_callback_on_writable(wsi);
619 }
620 return HttpDummy(wsi, reason, user, in, len);
621 }
622
LwsCallbackClientConnectionError(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)623 int NetWebSocketExec::LwsCallbackClientConnectionError(lws *wsi, lws_callback_reasons reason,
624 void *user, void *in, size_t len)
625 {
626 NETSTACK_LOGD("lws callback client connection error");
627 NETSTACK_LOGI("Lws client connection error %{public}s", (in == nullptr) ? "null" : reinterpret_cast<char *>(in));
628 // 200 means connect failed
629 OnConnectError(reinterpret_cast<CJWebsocketProxy *>(user), COMMON_ERROR_CODE, GetHttpResponseFromWsi(wsi));
630 return HttpDummy(wsi, reason, user, in, len);
631 }
632
LwsCallbackClientReceive(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)633 int NetWebSocketExec::LwsCallbackClientReceive(lws *wsi, lws_callback_reasons reason,
634 void *user, void *in, size_t len)
635 {
636 NETSTACK_LOGD("lws callback client receive");
637 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
638 auto isFinal = lws_is_final_fragment(wsi);
639 OnMessage(websocketProxy, in, len, lws_frame_is_binary(wsi), isFinal);
640 return HttpDummy(wsi, reason, user, in, len);
641 }
642
LwsCallbackClientFilterPreEstablish(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)643 int NetWebSocketExec::LwsCallbackClientFilterPreEstablish(lws *wsi, lws_callback_reasons reason,
644 void *user, void *in, size_t len)
645 {
646 NETSTACK_LOGD("lws callback client filter preEstablish");
647 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
648 auto webSocketContext = websocketProxy->GetWebSocketContext();
649 if (webSocketContext == nullptr) {
650 NETSTACK_LOGE("user data is null");
651 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
652 }
653
654 webSocketContext->openStatus = GetHttpResponseFromWsi(wsi);
655 char statusLine[MAX_HDR_LENGTH] = {0};
656 if (lws_hdr_copy(wsi, statusLine, MAX_HDR_LENGTH, WSI_TOKEN_HTTP) < 0 || strlen(statusLine) == 0) {
657 return HttpDummy(wsi, reason, user, in, len);
658 }
659
660 auto vec = CommonUtils::Split(statusLine, STATUS_LINE_SEP, STATUS_LINE_ELEM_NUM);
661 if (vec.size() >= FUNCTION_PARAM_TWO) {
662 webSocketContext->openMessage = vec[1];
663 }
664
665 char buffer[MAX_HDR_LENGTH] = {};
666 std::map<std::string, std::string> responseHeader;
667 for (int i = 0; i < WSI_TOKEN_COUNT; i++) {
668 if (lws_hdr_total_length(wsi, static_cast<lws_token_indexes>(i)) > 0) {
669 lws_hdr_copy(wsi, buffer, sizeof(buffer), static_cast<lws_token_indexes>(i));
670 std::string str;
671 if (lws_token_to_string(static_cast<lws_token_indexes>(i))) {
672 str =
673 std::string(reinterpret_cast<const char *>(lws_token_to_string(static_cast<lws_token_indexes>(i))));
674 }
675 if (!str.empty() && str.back() == ':') {
676 responseHeader.emplace(str.substr(0, str.size() - 1), std::string(buffer));
677 }
678 }
679 }
680 lws_hdr_custom_name_foreach(
681 wsi,
682 [](const char *name, int nlen, void *opaque) -> void {
683 auto header = static_cast<std::map<std::string, std::string> *>(opaque);
684 if (header == nullptr) {
685 return;
686 }
687 header->emplace(std::string(name).substr(0, nlen - 1), std::string(name).substr(nlen));
688 },
689 &responseHeader);
690 OnHeaderReceive(websocketProxy, responseHeader);
691 return HttpDummy(wsi, reason, user, in, len);
692 }
693
LwsCallbackClientEstablished(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)694 int NetWebSocketExec::LwsCallbackClientEstablished(lws *wsi, lws_callback_reasons reason,
695 void *user, void *in, size_t len)
696 {
697 NETSTACK_LOGD("lws callback client established");
698 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
699 auto webSocketContext = websocketProxy->GetWebSocketContext();
700 if (webSocketContext == nullptr) {
701 NETSTACK_LOGE("user data is null");
702 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
703 }
704 lws_callback_on_writable(wsi);
705 webSocketContext->SetLws(wsi);
706 OnOpen(reinterpret_cast<CJWebsocketProxy *>(user), webSocketContext->openStatus, webSocketContext->openMessage);
707 return HttpDummy(wsi, reason, user, in, len);
708 }
709
LwsCallbackClientClosed(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)710 int NetWebSocketExec::LwsCallbackClientClosed(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
711 {
712 NETSTACK_LOGD("lws callback client closed");
713 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
714 auto webSocketContext = websocketProxy->GetWebSocketContext();
715 if (webSocketContext == nullptr) {
716 NETSTACK_LOGE("user data is null");
717 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
718 }
719 webSocketContext->SetThreadStop(true);
720 if ((webSocketContext->closeReason).empty()) {
721 webSocketContext->Close(webSocketContext->closeStatus, LINK_DOWN);
722 }
723 if (webSocketContext->closeStatus == LWS_CLOSE_STATUS_NOSTATUS) {
724 NETSTACK_LOGE("The link is down, onError");
725 OnError(websocketProxy, COMMON_ERROR_CODE, GetHttpResponseFromWsi(wsi));
726 }
727 OnClose(reinterpret_cast<CJWebsocketProxy *>(user), webSocketContext->closeStatus, webSocketContext->closeReason);
728 return HttpDummy(wsi, reason, user, in, len);
729 }
730
LwsCallbackWsiDestroy(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)731 int NetWebSocketExec::LwsCallbackWsiDestroy(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
732 {
733 NETSTACK_LOGD("lws callback wsi destroy");
734 auto websocketProxy = reinterpret_cast<CJWebsocketProxy *>(user);
735 if (websocketProxy == nullptr) {
736 NETSTACK_LOGE("websocketProxy is null");
737 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
738 }
739 auto webSocketContext = websocketProxy->GetWebSocketContext();
740 if (webSocketContext == nullptr) {
741 NETSTACK_LOGE("user data is null");
742 return RaiseError(websocketProxy, GetHttpResponseFromWsi(wsi));
743 }
744 webSocketContext->SetLws(nullptr);
745 return HttpDummy(wsi, reason, user, in, len);
746 }
747
LwsCallbackProtocolDestroy(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)748 int NetWebSocketExec::LwsCallbackProtocolDestroy(lws *wsi, lws_callback_reasons reason,
749 void *user, void *in, size_t len)
750 {
751 NETSTACK_LOGD("lws callback protocol destroy");
752 return HttpDummy(wsi, reason, user, in, len);
753 }
754
LwsCallbackVhostCertAging(lws * wsi,lws_callback_reasons reason,void * user,void * in,size_t len)755 int NetWebSocketExec::LwsCallbackVhostCertAging(lws *wsi, lws_callback_reasons reason, void *user, void *in, size_t len)
756 {
757 NETSTACK_LOGI("lws callback vhost cert aging. len: %{public}zu", len);
758 return HttpDummy(wsi, reason, user, in, len);
759 }
760
FillContextInfo(WebSocketConnectContext * context,lws_context_creation_info & info,char * proxyAds)761 void NetWebSocketExec::FillContextInfo(WebSocketConnectContext *context,
762 lws_context_creation_info &info, char *proxyAds)
763 {
764 info.options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT;
765 info.port = CONTEXT_PORT_NO_LISTEN;
766 info.protocols = WEBSOCKET_PROTOCOLS;
767 info.fd_limit_per_thread = FD_LIMIT_PER_THREAD;
768
769 char tempUri[MAX_URI_LENGTH] = {0};
770 const char *tempProtocol = nullptr;
771 const char *tempAddress = nullptr;
772 const char *tempPath = nullptr;
773 int32_t tempPort = 0;
774
775 std::string host;
776 uint32_t port = 0;
777 std::string exclusions;
778
779 if (strcpy_s(tempUri, MAX_URI_LENGTH, context->url.c_str()) < 0) {
780 NETSTACK_LOGE("strcpy_s failed");
781 return;
782 }
783 if (lws_parse_uri(tempUri, &tempProtocol, &tempAddress, &tempPort, &tempPath) != 0) {
784 NETSTACK_LOGE("get websocket hostname failed");
785 return;
786 }
787 GetWebsocketProxyInfo(context, host, port, exclusions);
788 if (!host.empty() && !CommonUtils::IsHostNameExcluded(tempAddress, exclusions, ",")) {
789 if (strcpy_s(proxyAds, host.length() + 1, host.c_str()) != ERR_OK) {
790 NETSTACK_LOGE("memory copy failed");
791 }
792 info.http_proxy_address = proxyAds;
793 info.http_proxy_port = port;
794 }
795 }
796
CheckFilePath(std::string & path)797 static bool CheckFilePath(std::string &path)
798 {
799 char tmpPath[PATH_MAX] = {0};
800 if (!realpath(static_cast<const char *>(path.c_str()), tmpPath)) {
801 NETSTACK_LOGE("path is error");
802 return false;
803 }
804 path = tmpPath;
805 return true;
806 }
807
FillCaPath(WebSocketConnectContext * context,lws_context_creation_info & info)808 bool NetWebSocketExec::FillCaPath(WebSocketConnectContext *context, lws_context_creation_info &info)
809 {
810 if (!context->caPath_.empty()) {
811 if (!CheckFilePath(context->caPath_)) {
812 NETSTACK_LOGE("ca not exist");
813 context->SetErrorCode(WEBSOCKET_ERROR_CODE_FILE_NOT_EXIST);
814 return false;
815 }
816 info.client_ssl_ca_filepath = context->caPath_.c_str();
817 NETSTACK_LOGD("load customize CA: %{public}s", info.client_ssl_ca_filepath);
818 } else {
819 info.client_ssl_ca_dirs[0] = WEBSOCKET_SYSTEM_PREPARE_CA_PATH;
820 #ifdef HAS_NETMANAGER_BASE
821 if (NetManagerStandard::NetworkSecurityConfig::GetInstance().TrustUserCa()) {
822 context->userCertPath_ = BASE_PATH + std::to_string(getuid() / UID_TRANSFORM_DIVISOR);
823 info.client_ssl_ca_dirs[1] = context->userCertPath_.c_str();
824 }
825 #endif
826 NETSTACK_LOGD("load system CA");
827 }
828 if (!context->clientCert_.empty()) {
829 char realKeyPath[PATH_MAX] = {0};
830 if (!CheckFilePath(context->clientCert_) || !realpath(context->clientKey_.Data(), realKeyPath)) {
831 NETSTACK_LOGE("client cert not exist");
832 context->SetErrorCode(WEBSOCKET_ERROR_CODE_FILE_NOT_EXIST);
833 return false;
834 }
835 context->clientKey_ = SecureChar(realKeyPath);
836 info.client_ssl_cert_filepath = context->clientCert_.c_str();
837 info.client_ssl_private_key_filepath = context->clientKey_.Data();
838 info.client_ssl_private_key_password = context->keyPassword_.Data();
839 }
840 return true;
841 }
842
GetWebsocketProxyInfo(WebSocketConnectContext * context,std::string & host,uint32_t & port,std::string & exclusions)843 void NetWebSocketExec::GetWebsocketProxyInfo(WebSocketConnectContext *context, std::string &host,
844 uint32_t &port, std::string &exclusions)
845 {
846 if (context->GetUsingWebsocketProxyType() == WebsocketProxyType::USE_SYSTEM) {
847 #ifdef HAS_NETMANAGER_BASE
848 using namespace NetManagerStandard;
849 HttpProxy websocketProxy;
850 NetConnClient::GetInstance().GetDefaultHttpProxy(websocketProxy);
851 host = websocketProxy.GetHost();
852 port = websocketProxy.GetPort();
853 exclusions = CommonUtils::ToString(websocketProxy.GetExclusionList());
854 #endif
855 } else if (context->GetUsingWebsocketProxyType() == WebsocketProxyType::USE_SPECIFIED) {
856 context->GetSpecifiedWebsocketProxy(host, port, exclusions);
857 }
858 }
859
GetHttpResponseFromWsi(lws * wsi)860 uint32_t NetWebSocketExec::GetHttpResponseFromWsi(lws *wsi)
861 {
862 if (wsi == nullptr) {
863 return 0;
864 }
865 return lws_http_client_http_response(wsi);
866 }
867
OnOpen(CJWebsocketProxy * websocketProxy,uint32_t status,const std::string & message)868 void NetWebSocketExec::OnOpen(CJWebsocketProxy *websocketProxy, uint32_t status, const std::string &message)
869 {
870 NETSTACK_LOGI("OnOpen %{public}u %{public}s", status, message.c_str());
871 if (websocketProxy == nullptr) {
872 NETSTACK_LOGE("websocketProxy is null");
873 return;
874 }
875 if (websocketProxy->FindCallback(EVENT_OPEN) == std::nullopt) {
876 NETSTACK_LOGI("no event listener: OPEN");
877 return;
878 }
879
880 CWebSocketCallbackData* para = new CWebSocketCallbackData;
881 para->code = ERR_OK;
882 para->typeId = EVENT_OPEN;
883 para->data = CreateOpenPara(status, message);
884 para->dataLen = sizeof(COpenResponse);
885 websocketProxy->EmitCallBack(para);
886 auto openResponse = reinterpret_cast<COpenResponse*>(para->data);
887 free(openResponse->message);
888 delete openResponse;
889 delete para;
890 }
891
OnError(CJWebsocketProxy * websocketProxy,int32_t code,uint32_t httpResponse)892 void NetWebSocketExec::OnError(CJWebsocketProxy *websocketProxy, int32_t code, uint32_t httpResponse)
893 {
894 NETSTACK_LOGI("OnError code is %{public}d, httpResponse is %{public}d", code, httpResponse);
895 if (websocketProxy == nullptr) {
896 NETSTACK_LOGE("websocketProxy is null");
897 return;
898 }
899 if (websocketProxy->FindCallback(EVENT_OPEN) == std::nullopt) {
900 NETSTACK_LOGI("no event listener: ERROR");
901 return;
902 }
903
904 CWebSocketCallbackData* para = new CWebSocketCallbackData;
905 para->code = ERR_OK;
906 para->typeId = EVENT_ERROR;
907 para->data = CreateError(code, httpResponse);
908 para->dataLen = sizeof(CErrorResponse);
909 websocketProxy->EmitCallBack(para);
910 delete reinterpret_cast<CErrorResponse*>(para->data);
911 delete para;
912 }
913
OnMessage(CJWebsocketProxy * websocketProxy,void * data,size_t length,bool isBinary,bool isFinal)914 void NetWebSocketExec::OnMessage(CJWebsocketProxy *websocketProxy, void *data,
915 size_t length, bool isBinary, bool isFinal)
916 {
917 NETSTACK_LOGD("OnMessage %{public}d", isBinary);
918 if (websocketProxy == nullptr) {
919 NETSTACK_LOGE("websocketProxy is null");
920 return;
921 }
922 if (websocketProxy->FindCallback(EVENT_OPEN) == std::nullopt) {
923 NETSTACK_LOGI("no event listener: MESSAGE");
924 return;
925 }
926 if (length > INT32_MAX) {
927 NETSTACK_LOGE("data length too long");
928 return;
929 }
930 HandleRcvMessage(websocketProxy, data, length, isBinary, isFinal);
931 }
932
OnClose(CJWebsocketProxy * websocketProxy,lws_close_status closeStatus,const std::string & closeReason)933 void NetWebSocketExec::OnClose(CJWebsocketProxy *websocketProxy,
934 lws_close_status closeStatus, const std::string &closeReason)
935 {
936 NETSTACK_LOGI("OnClose %{public}u %{public}s", closeStatus, closeReason.c_str());
937 if (websocketProxy == nullptr) {
938 NETSTACK_LOGE("websocketProxy is null");
939 return;
940 }
941 if (websocketProxy->FindCallback(EVENT_CLOSE) == std::nullopt) {
942 NETSTACK_LOGI("no event listener: CLOSE");
943 return;
944 }
945
946 CWebSocketCallbackData* para = new CWebSocketCallbackData;
947 para->code = ERR_OK;
948 para->typeId = EVENT_CLOSE;
949 para->data = CreateClosePara(closeStatus, closeReason);
950 para->dataLen = sizeof(CCloseResponse);
951 websocketProxy->EmitCallBack(para);
952 auto closeResponse = reinterpret_cast<CCloseResponse*>(para->data);
953 free(closeResponse->reason);
954 delete closeResponse;
955 delete para;
956 }
957
OnDataEnd(CJWebsocketProxy * websocketProxy)958 void NetWebSocketExec::OnDataEnd(CJWebsocketProxy *websocketProxy)
959 {
960 if (websocketProxy == nullptr) {
961 NETSTACK_LOGE("websocketProxy is null");
962 return;
963 }
964 if (websocketProxy->FindCallback(EVENT_DATA_END) == std::nullopt) {
965 NETSTACK_LOGI("no event listener: EVENT_DATA_END");
966 return;
967 }
968 CWebSocketCallbackData* para = new CWebSocketCallbackData;
969 para->code = ERR_OK;
970 para->typeId = EVENT_DATA_END;
971 websocketProxy->EmitCallBack(para);
972 delete para;
973 }
974
OnHeaderReceive(CJWebsocketProxy * websocketProxy,const std::map<std::string,std::string> & headers)975 void NetWebSocketExec::OnHeaderReceive(CJWebsocketProxy *websocketProxy,
976 const std::map<std::string, std::string> &headers)
977 {
978 if (websocketProxy == nullptr) {
979 NETSTACK_LOGE("websocketProxy is null");
980 return;
981 }
982 if (websocketProxy->FindCallback(EVENT_HEADER_RECEIVE) == std::nullopt) {
983 NETSTACK_LOGI("no event listener: EVENT_HEADER_RECEIVE");
984 return;
985 }
986
987 CWebSocketCallbackData* para = new CWebSocketCallbackData;
988 para->code = ERR_OK;
989 para->typeId = EVENT_HEADER_RECEIVE;
990 para->data = CreateResponseHeader(headers);
991 para->dataLen = sizeof(CReceiveResponse);
992 websocketProxy->EmitCallBack(para);
993 auto receiveResponse = reinterpret_cast<CReceiveResponse*>(para->data);
994 FreeCArrString(receiveResponse->header);
995 delete receiveResponse;
996 delete para;
997 }
998
HandleRcvMessage(CJWebsocketProxy * websocketProxy,void * data,size_t length,bool isBinary,bool isFinal)999 void NetWebSocketExec::HandleRcvMessage(CJWebsocketProxy *websocketProxy,
1000 void *data, size_t length, bool isBinary, bool isFinal)
1001 {
1002 if (isBinary) {
1003 websocketProxy->AppendWebSocketBinaryData(data, length);
1004 if (isFinal) {
1005 const std::string &msgFromManager = websocketProxy->GetWebSocketBinaryData();
1006 auto msg = new std::string;
1007 msg->append(msgFromManager.data(), msgFromManager.size());
1008 websocketProxy->SetQueueData(msg);
1009 CWebSocketCallbackData* para = new CWebSocketCallbackData;
1010 para->code = ERR_OK;
1011 para->typeId = EVENT_MESSAGE;
1012 para->data = CreateMessagePara(websocketProxy, isBinary);
1013 para->dataLen = sizeof(CMessageResponse);
1014 websocketProxy->EmitCallBack(para);
1015 if (para->data) {
1016 auto msgResponse = reinterpret_cast<CMessageResponse*>(para->data);
1017 free(msgResponse->result.head);
1018 delete msgResponse;
1019 }
1020 delete para;
1021 websocketProxy->ClearWebSocketTextData();
1022 OnDataEnd(websocketProxy);
1023 }
1024 } else {
1025 websocketProxy->AppendWebSocketTextData(data, length);
1026 if (isFinal) {
1027 const std::string &msgFromManager = websocketProxy->GetWebSocketTextData();
1028 auto msg = new (std::nothrow) std::string;
1029 if (msg == nullptr) {
1030 return;
1031 }
1032 msg->append(msgFromManager.data(), msgFromManager.size());
1033 websocketProxy->SetQueueData(msg);
1034 CWebSocketCallbackData* para = new CWebSocketCallbackData;
1035 para->code = ERR_OK;
1036 para->typeId = EVENT_MESSAGE;
1037 para->data = CreateMessagePara(websocketProxy, isBinary);
1038 para->dataLen = sizeof(CMessageResponse);
1039 websocketProxy->EmitCallBack(para);
1040 if (para->data) {
1041 auto msgResponse = reinterpret_cast<CMessageResponse*>(para->data);
1042 free(msgResponse->result.head);
1043 delete msgResponse;
1044 }
1045 delete para;
1046 websocketProxy->ClearWebSocketTextData();
1047 OnDataEnd(websocketProxy);
1048 }
1049 }
1050 }
1051 } // namespace OHOS::NetStack::NetWebSocket