1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <stdio.h> 6 #include <sstream> 7 8 #include "ppapi/cpp/completion_callback.h" 9 #include "ppapi/cpp/instance.h" 10 #include "ppapi/cpp/module.h" 11 #include "ppapi/cpp/var.h" 12 #include "ppapi/cpp/var_array_buffer.h" 13 #include "ppapi/cpp/websocket.h" 14 15 class WebSocketInstance : public pp::Instance { 16 public: 17 explicit WebSocketInstance(PP_Instance instance) 18 : pp::Instance(instance), websocket_(NULL) {} 19 virtual ~WebSocketInstance() {} 20 virtual void HandleMessage(const pp::Var& var_message); 21 22 private: 23 bool IsConnected(); 24 25 void Open(const std::string& url); 26 void Close(); 27 void SendAsBinary(const std::string& message); 28 void SendAsText(const std::string& message); 29 void Receive(); 30 31 void OnConnectCompletion(int32_t result); 32 void OnCloseCompletion(int32_t result); 33 void OnReceiveCompletion(int32_t result); 34 35 static void OnConnectCompletionCallback(void* user_data, int32_t result); 36 static void OnCloseCompletionCallback(void* user_data, int32_t result); 37 static void OnReceiveCompletionCallback(void* user_data, int32_t result); 38 39 pp::WebSocket* websocket_; 40 pp::Var receive_var_; 41 }; 42 43 #define MAX_TO_CONVERT 8 44 #define BYTES_PER_CHAR 4 45 #define TAIL_AND_NUL_SIZE 4 46 47 static std::string ArrayToString(pp::VarArrayBuffer& array) { 48 char tmp[MAX_TO_CONVERT * BYTES_PER_CHAR + TAIL_AND_NUL_SIZE]; 49 uint32_t offs = 0; 50 uint8_t* data = static_cast<uint8_t*>(array.Map()); 51 52 for (offs = 0; offs < array.ByteLength() && offs < MAX_TO_CONVERT; offs++) 53 sprintf(&tmp[offs * BYTES_PER_CHAR], "%02Xh ", data[offs]); 54 55 sprintf(&tmp[offs * BYTES_PER_CHAR], "..."); 56 array.Unmap(); 57 return std::string(tmp); 58 } 59 60 void WebSocketInstance::HandleMessage(const pp::Var& var_message) { 61 if (!var_message.is_string()) 62 return; 63 std::string message = var_message.AsString(); 64 // This message must contain a command character followed by ';' and 65 // arguments like "X;arguments". 66 if (message.length() < 2 || message[1] != ';') 67 return; 68 switch (message[0]) { 69 case 'o': 70 // The command 'o' requests to open the specified URL. 71 // URL is passed as an argument like "o;URL". 72 Open(message.substr(2)); 73 break; 74 case 'c': 75 // The command 'c' requests to close without any argument like "c;" 76 Close(); 77 break; 78 case 'b': 79 // The command 'b' requests to send a message as a binary frame. The 80 // message is passed as an argument like "b;message". 81 SendAsBinary(message.substr(2)); 82 break; 83 case 't': 84 // The command 't' requests to send a message as a text frame. The message 85 // is passed as an argument like "t;message". 86 SendAsText(message.substr(2)); 87 break; 88 } 89 } 90 91 bool WebSocketInstance::IsConnected() { 92 if (!websocket_) 93 return false; 94 if (websocket_->GetReadyState() != PP_WEBSOCKETREADYSTATE_OPEN) 95 return false; 96 return true; 97 } 98 99 void WebSocketInstance::Open(const std::string& url) { 100 pp::CompletionCallback callback(OnConnectCompletionCallback, this); 101 websocket_ = new pp::WebSocket(this); 102 if (!websocket_) 103 return; 104 websocket_->Connect(pp::Var(url), NULL, 0, callback); 105 PostMessage(pp::Var("connecting...")); 106 } 107 108 void WebSocketInstance::Close() { 109 if (!IsConnected()) 110 return; 111 pp::CompletionCallback callback(OnCloseCompletionCallback, this); 112 websocket_->Close( 113 PP_WEBSOCKETSTATUSCODE_NORMAL_CLOSURE, pp::Var("bye"), callback); 114 } 115 116 void WebSocketInstance::SendAsBinary(const std::string& message) { 117 if (!IsConnected()) 118 return; 119 uint32_t size = message.size(); 120 pp::VarArrayBuffer array_buffer(size); 121 char* data = static_cast<char*>(array_buffer.Map()); 122 for (uint32_t i = 0; i < size; ++i) 123 data[i] = message[i]; 124 array_buffer.Unmap(); 125 websocket_->SendMessage(array_buffer); 126 std::string message_text = ArrayToString(array_buffer); 127 PostMessage(pp::Var("send (binary): " + message_text)); 128 } 129 130 void WebSocketInstance::SendAsText(const std::string& message) { 131 if (!IsConnected()) 132 return; 133 websocket_->SendMessage(pp::Var(message)); 134 PostMessage(pp::Var("send (text): " + message)); 135 } 136 137 void WebSocketInstance::Receive() { 138 pp::CompletionCallback callback(OnReceiveCompletionCallback, this); 139 // |receive_var_| must be valid until |callback| is invoked. 140 // Just use a member variable. 141 websocket_->ReceiveMessage(&receive_var_, callback); 142 } 143 144 void WebSocketInstance::OnConnectCompletion(int32_t result) { 145 if (result != PP_OK) { 146 PostMessage(pp::Var("connection failed")); 147 return; 148 } 149 PostMessage(pp::Var("connected")); 150 Receive(); 151 } 152 153 void WebSocketInstance::OnCloseCompletion(int32_t result) { 154 PostMessage(pp::Var(PP_OK == result ? "closed" : "abnormally closed")); 155 } 156 157 void WebSocketInstance::OnReceiveCompletion(int32_t result) { 158 if (result == PP_OK) { 159 if (receive_var_.is_array_buffer()) { 160 pp::VarArrayBuffer array_buffer(receive_var_); 161 std::string message_text = ArrayToString(array_buffer); 162 PostMessage("receive (binary): " + message_text); 163 } 164 else { 165 PostMessage("receive (text): " + receive_var_.AsString()); 166 } 167 } 168 Receive(); 169 } 170 171 void WebSocketInstance::OnConnectCompletionCallback(void* user_data, 172 int32_t result) { 173 WebSocketInstance* instance = static_cast<WebSocketInstance*>(user_data); 174 instance->OnConnectCompletion(result); 175 } 176 177 void WebSocketInstance::OnCloseCompletionCallback(void* user_data, 178 int32_t result) { 179 WebSocketInstance* instance = static_cast<WebSocketInstance*>(user_data); 180 instance->OnCloseCompletion(result); 181 } 182 183 void WebSocketInstance::OnReceiveCompletionCallback(void* user_data, 184 int32_t result) { 185 WebSocketInstance* instance = static_cast<WebSocketInstance*>(user_data); 186 instance->OnReceiveCompletion(result); 187 } 188 189 // The WebSocketModule provides an implementation of pp::Module that creates 190 // WebSocketInstance objects when invoked. 191 class WebSocketModule : public pp::Module { 192 public: 193 WebSocketModule() : pp::Module() {} 194 virtual ~WebSocketModule() {} 195 196 virtual pp::Instance* CreateInstance(PP_Instance instance) { 197 return new WebSocketInstance(instance); 198 } 199 }; 200 201 // Implement the required pp::CreateModule function that creates our specific 202 // kind of Module. 203 namespace pp { 204 Module* CreateModule() { return new WebSocketModule(); } 205 } // namespace pp 206