1 // Copyright (c) 2013 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 <cstdarg> 6 #include <cstdio> 7 #include <cstdlib> 8 #include <cstring> 9 10 #include "ppapi/c/ppb_console.h" 11 #include "ppapi/cpp/extensions/dev/socket_dev.h" 12 #include "ppapi/cpp/instance.h" 13 #include "ppapi/cpp/module.h" 14 #include "ppapi/cpp/var.h" 15 #include "ppapi/cpp/var_array_buffer.h" 16 #include "ppapi/tests/test_utils.h" 17 18 using namespace pp; 19 using namespace pp::ext; 20 21 namespace { 22 23 const char* const kSendContents = "0100000005320000005hello"; 24 const char* const kReceiveContentsPrefix = "0100000005320000005"; 25 const size_t kReceiveContentsSuffixSize = 11; 26 27 const char* const kMulticastAddress = "237.132.100.133"; 28 const int32_t kMulticastPort = 11103; 29 const char* const kMulticastMessage = "hello world!"; 30 31 } // namespace 32 33 class MyInstance : public Instance { 34 public: 35 explicit MyInstance(PP_Instance instance) 36 : Instance(instance), 37 socket_(InstanceHandle(instance)), 38 console_interface_(NULL), 39 port_(0) { 40 } 41 virtual ~MyInstance() { 42 } 43 44 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { 45 console_interface_ = static_cast<const PPB_Console*>( 46 Module::Get()->GetBrowserInterface(PPB_CONSOLE_INTERFACE)); 47 48 if (!console_interface_) 49 return false; 50 51 PostMessage(Var("ready")); 52 return true; 53 } 54 55 virtual void HandleMessage(const pp::Var& message_data) { 56 std::string output; 57 do { 58 if (!message_data.is_string()) { 59 output = "Invalid control message."; 60 break; 61 } 62 63 std::string control_message = message_data.AsString(); 64 std::vector<std::string> parts; 65 size_t pos = 0; 66 size_t next_match = 0; 67 while (pos < control_message.size()) { 68 next_match = control_message.find(':', pos); 69 if (next_match == std::string::npos) 70 next_match = control_message.size(); 71 parts.push_back(control_message.substr(pos, next_match - pos)); 72 pos = next_match + 1; 73 } 74 75 if (parts.size() != 3) { 76 output = "Invalid protocol/address/port input."; 77 break; 78 } 79 80 test_type_ = parts[0]; 81 address_ = parts[1]; 82 port_ = atoi(parts[2].c_str()); 83 Log(PP_LOGLEVEL_LOG, "Running tests, protocol %s, server %s:%d", 84 test_type_.c_str(), address_.c_str(), port_); 85 86 if (test_type_ == "tcp_server") { 87 output = TestServerSocket(); 88 } else if (test_type_ == "multicast") { 89 output = TestMulticast(); 90 } else { 91 output = TestClientSocket(); 92 } 93 } while (false); 94 95 NotifyTestDone(output); 96 } 97 98 private: 99 std::string TestServerSocket() { 100 int32_t socket_id = 0; 101 { 102 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 103 callback(pp_instance()); 104 callback.WaitForResult(socket_.Create( 105 socket::SocketType_Dev(socket::SocketType_Dev::TCP), 106 Optional<socket::CreateOptions_Dev>(), callback.GetCallback())); 107 if (callback.result() != PP_OK) 108 return "Create(): failed."; 109 socket_id = callback.output().socket_id(); 110 if (socket_id <= 0) 111 return "Create(): invalid socket ID."; 112 } 113 114 { 115 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 116 callback.WaitForResult(socket_.Listen( 117 socket_id, address_, port_, Optional<int32_t>(), 118 callback.GetCallback())); 119 if (callback.result() != PP_OK) 120 return "Listen(): failed."; 121 if (callback.output() != 0) 122 return "Listen(): failed."; 123 } 124 125 int32_t client_socket_id = 0; 126 { 127 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 128 callback(pp_instance()); 129 callback.WaitForResult(socket_.Create( 130 socket::SocketType_Dev(socket::SocketType_Dev::TCP), 131 Optional<socket::CreateOptions_Dev>(), callback.GetCallback())); 132 if (callback.result() != PP_OK) 133 return "Create(): failed."; 134 client_socket_id = callback.output().socket_id(); 135 if (client_socket_id <= 0) 136 return "Create(): invalid socket ID."; 137 } 138 139 { 140 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 141 callback.WaitForResult(socket_.Connect( 142 client_socket_id, address_, port_, callback.GetCallback())); 143 if (callback.result() != PP_OK) 144 return "Connect(): failed."; 145 if (callback.output() != 0) 146 return "Connect(): failed."; 147 } 148 149 int32_t accepted_socket_id = 0; 150 { 151 TestExtCompletionCallbackWithOutput<socket::AcceptInfo_Dev> 152 callback(pp_instance()); 153 callback.WaitForResult(socket_.Accept(socket_id, callback.GetCallback())); 154 if (callback.result() != PP_OK) 155 return "Accept(): failed."; 156 socket::AcceptInfo_Dev accept_info = callback.output(); 157 if (accept_info.result_code() != 0 || !accept_info.socket_id().IsSet()) 158 return "Accept(): failed."; 159 accepted_socket_id = *accept_info.socket_id(); 160 } 161 162 size_t bytes_written = 0; 163 { 164 TestExtCompletionCallbackWithOutput<socket::WriteInfo_Dev> 165 callback(pp_instance()); 166 VarArrayBuffer array_buffer = ConvertToArrayBuffer(kSendContents); 167 callback.WaitForResult(socket_.Write(client_socket_id, array_buffer, 168 callback.GetCallback())); 169 if (callback.result() != PP_OK) 170 return "Write(): failed."; 171 socket::WriteInfo_Dev write_info = callback.output(); 172 bytes_written = static_cast<size_t>(write_info.bytes_written()); 173 if (bytes_written <= 0) 174 return "Write(): did not write any bytes."; 175 } 176 177 { 178 TestExtCompletionCallbackWithOutput<socket::ReadInfo_Dev> 179 callback(pp_instance()); 180 callback.WaitForResult(socket_.Read( 181 accepted_socket_id, Optional<int32_t>(), callback.GetCallback())); 182 if (callback.result() != PP_OK) 183 return "Read(): failed."; 184 185 std::string data_string = ConvertFromArrayBuffer( 186 &callback.output().data()); 187 if (data_string.compare(0, std::string::npos, kSendContents, 188 bytes_written) != 0) { 189 return "Read(): Received data does not match."; 190 } 191 } 192 193 socket_.Destroy(client_socket_id); 194 socket_.Destroy(accepted_socket_id); 195 socket_.Destroy(socket_id); 196 return std::string(); 197 } 198 199 std::string TestMulticast() { 200 int32_t socket_id = 0; 201 { 202 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 203 callback(pp_instance()); 204 callback.WaitForResult(socket_.Create( 205 socket::SocketType_Dev::UDP, Optional<socket::CreateOptions_Dev>(), 206 callback.GetCallback())); 207 if (callback.result() != PP_OK) 208 return "Create(): failed."; 209 socket_id = callback.output().socket_id(); 210 if (socket_id <= 0) 211 return "Create(): invalid socket ID."; 212 } 213 214 { 215 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 216 callback.WaitForResult(socket_.SetMulticastTimeToLive( 217 socket_id, 0, callback.GetCallback())); 218 if (callback.result() != PP_OK || callback.output() != 0) 219 return "SetMulticastTimeToLive(): failed."; 220 } 221 222 { 223 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 224 callback.WaitForResult(socket_.SetMulticastTimeToLive( 225 socket_id, -3, callback.GetCallback())); 226 if (callback.result() == PP_OK) 227 return "SetMulticastTimeToLive(): succeeded unexpectedly."; 228 if (callback.output() != -4) 229 return "SetMulticastTimeToLive(): returned unexpected result."; 230 } 231 232 { 233 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 234 callback.WaitForResult(socket_.SetMulticastLoopbackMode( 235 socket_id, false, callback.GetCallback())); 236 if (callback.result() != PP_OK || callback.output() != 0) 237 return "SetMulticastLoopbackMode(): failed."; 238 } 239 240 { 241 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 242 callback.WaitForResult(socket_.SetMulticastLoopbackMode( 243 socket_id, true, callback.GetCallback())); 244 if (callback.result() != PP_OK || callback.output() != 0) 245 return "SetMulticastLoopbackMode(): failed."; 246 } 247 248 socket_.Destroy(socket_id); 249 socket_id = 0; 250 251 int32_t server_socket_id = 0; 252 { 253 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 254 callback(pp_instance()); 255 callback.WaitForResult(socket_.Create( 256 socket::SocketType_Dev::UDP, Optional<socket::CreateOptions_Dev>(), 257 callback.GetCallback())); 258 if (callback.result() != PP_OK) 259 return "Create(): failed."; 260 server_socket_id = callback.output().socket_id(); 261 if (server_socket_id <= 0) 262 return "Create(): invalid socket ID."; 263 } 264 265 { 266 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 267 callback.WaitForResult(socket_.Bind( 268 server_socket_id, "0.0.0.0", kMulticastPort, callback.GetCallback())); 269 if (callback.result() != PP_OK || callback.output() != 0) 270 return "Bind(): failed"; 271 } 272 273 { 274 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 275 callback.WaitForResult(socket_.JoinGroup( 276 server_socket_id, kMulticastAddress, callback.GetCallback())); 277 if (callback.result() != PP_OK || callback.output() != 0) 278 return "JoinGroup(): failed."; 279 } 280 281 { 282 TestExtCompletionCallbackWithOutput<std::vector<std::string> > 283 callback(pp_instance()); 284 callback.WaitForResult(socket_.GetJoinedGroups( 285 server_socket_id, callback.GetCallback())); 286 if (callback.result() != PP_OK) 287 return "GetJoinedGroups(): failed."; 288 std::vector<std::string> groups = callback.output(); 289 if (groups.size() != 1 || groups[0] != kMulticastAddress) { 290 return "GetJoinedGroups(): the returned groups didn't match those " 291 "joined."; 292 } 293 } 294 295 int32_t client_socket_id = 0; 296 { 297 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 298 callback(pp_instance()); 299 callback.WaitForResult(socket_.Create( 300 socket::SocketType_Dev::UDP, Optional<socket::CreateOptions_Dev>(), 301 callback.GetCallback())); 302 if (callback.result() != PP_OK) 303 return "Create(): failed."; 304 client_socket_id = callback.output().socket_id(); 305 if (client_socket_id <= 0) 306 return "Create(): invalid socket ID."; 307 } 308 309 { 310 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 311 callback.WaitForResult(socket_.SetMulticastTimeToLive( 312 client_socket_id, 0, callback.GetCallback())); 313 if (callback.result() != PP_OK || callback.output() != 0) 314 return "SetMulticastTimeToLive(): failed."; 315 } 316 317 { 318 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 319 callback.WaitForResult(socket_.Connect( 320 client_socket_id, kMulticastAddress, kMulticastPort, 321 callback.GetCallback())); 322 if (callback.result() != PP_OK || callback.output() != 0) 323 return "Connnect(): failed."; 324 } 325 326 { 327 VarArrayBuffer input_array_buffer = 328 ConvertToArrayBuffer(kMulticastMessage); 329 size_t bytes_written = 0; 330 int32_t result_code = 0; 331 VarArrayBuffer data; 332 333 TestExtCompletionCallbackWithOutput<socket::RecvFromInfo_Dev> 334 recv_from_callback(pp_instance()); 335 int32_t recv_from_result = socket_.RecvFrom( 336 server_socket_id, 1024, recv_from_callback.GetCallback()); 337 if (recv_from_result != PP_OK_COMPLETIONPENDING) 338 return "RecvFrom(): did not wait for data."; 339 340 TestExtCompletionCallbackWithOutput<socket::WriteInfo_Dev> 341 write_callback(pp_instance()); 342 write_callback.WaitForResult(socket_.Write( 343 client_socket_id, input_array_buffer, write_callback.GetCallback())); 344 if (write_callback.result() != PP_OK) 345 return "Write(): failed."; 346 bytes_written = static_cast<size_t>( 347 write_callback.output().bytes_written()); 348 349 recv_from_callback.WaitForResult(recv_from_result); 350 if (recv_from_callback.result() != PP_OK) 351 return "RecvFrom(): failed."; 352 socket::RecvFromInfo_Dev recv_from_info = recv_from_callback.output(); 353 result_code = recv_from_info.result_code(); 354 data = recv_from_info.data(); 355 356 if (bytes_written != strlen(kMulticastMessage)) 357 return "Write(): did not send the whole data buffer."; 358 359 if (result_code > 0 && 360 static_cast<uint32_t>(result_code) != data.ByteLength()) { 361 return "RecvFrom(): inconsistent result code and byte length."; 362 } 363 364 std::string output_string = ConvertFromArrayBuffer(&data); 365 if (output_string != kMulticastMessage) { 366 return std::string("RecvFrom(): mismatched data: ").append( 367 output_string); 368 } 369 } 370 371 { 372 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 373 callback.WaitForResult(socket_.LeaveGroup( 374 server_socket_id, kMulticastAddress, callback.GetCallback())); 375 if (callback.result() != PP_OK || callback.output() != 0) 376 return "LeaveGroup(): failed."; 377 } 378 379 socket_.Destroy(server_socket_id); 380 socket_.Destroy(client_socket_id); 381 return std::string(); 382 } 383 384 std::string TestClientSocket() { 385 socket::SocketType_Dev socket_type; 386 if (!socket_type.Populate(Var(test_type_).pp_var())) 387 return "Invalid socket type."; 388 389 int32_t socket_id = 0; 390 { 391 TestExtCompletionCallbackWithOutput<socket::CreateInfo_Dev> 392 callback(pp_instance()); 393 callback.WaitForResult(socket_.Create( 394 socket_type, Optional<socket::CreateOptions_Dev>(), 395 callback.GetCallback())); 396 if (callback.result() != PP_OK) 397 return "Create(): failed."; 398 socket_id = callback.output().socket_id(); 399 if (socket_id <= 0) 400 return "Create(): invalid socket ID."; 401 } 402 403 { 404 TestExtCompletionCallbackWithOutput<socket::SocketInfo_Dev> 405 callback(pp_instance()); 406 callback.WaitForResult(socket_.GetInfo(socket_id, 407 callback.GetCallback())); 408 if (callback.result() != PP_OK) 409 return "GetInfo(): failed."; 410 411 socket::SocketInfo_Dev socket_info = callback.output(); 412 if (socket_info.socket_type().value != socket_type.value) 413 return "GetInfo(): inconsistent socket type."; 414 if (socket_info.connected()) 415 return "GetInfo(): socket should not be connected."; 416 if (socket_info.peer_address().IsSet() || socket_info.peer_port().IsSet()) 417 return "GetInfo(): unconnected socket should not have peer."; 418 if (socket_info.local_address().IsSet() || 419 socket_info.local_port().IsSet()) { 420 return "GetInfo(): unconnected socket should not have local binding."; 421 } 422 } 423 424 { 425 if (socket_type.value == socket::SocketType_Dev::TCP) { 426 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 427 callback.WaitForResult(socket_.Connect( 428 socket_id, address_, port_, callback.GetCallback())); 429 if (callback.result() != PP_OK) 430 return "Connect(): failed."; 431 if (callback.output() != 0) 432 return "Connect(): failed."; 433 } else { 434 TestExtCompletionCallbackWithOutput<int32_t> callback(pp_instance()); 435 callback.WaitForResult(socket_.Bind( 436 socket_id, "0.0.0.0", 0, callback.GetCallback())); 437 if (callback.result() != PP_OK) 438 return "Bind(): failed."; 439 if (callback.output() != 0) 440 return "Bind(): failed."; 441 } 442 } 443 444 { 445 TestExtCompletionCallbackWithOutput<socket::SocketInfo_Dev> 446 callback(pp_instance()); 447 callback.WaitForResult(socket_.GetInfo(socket_id, 448 callback.GetCallback())); 449 if (callback.result() != PP_OK) 450 return "GetInfo(): failed."; 451 452 socket::SocketInfo_Dev socket_info = callback.output(); 453 if (socket_info.socket_type().value != socket_type.value) 454 return "GetInfo(): inconsistent socket type."; 455 if (!socket_info.local_address().IsSet() || 456 !socket_info.local_port().IsSet()) { 457 return "GetInfo(): bound socket should have local address and port."; 458 } 459 if (socket_type.value == socket::SocketType_Dev::TCP) { 460 if (!socket_info.connected()) 461 return "GetInfo(): TCP socket should be connected."; 462 if (!socket_info.peer_address().IsSet() || 463 !socket_info.peer_port().IsSet()) { 464 return "GetInfo(): connected TCP socket should have peer address and " 465 "port"; 466 } 467 if (*socket_info.peer_address() != "127.0.0.1" || 468 *socket_info.peer_port() != port_) { 469 return "GetInfo(): peer address and port should match the listening " 470 "server."; 471 } 472 } else { 473 if (socket_info.connected()) 474 return "GetInfo(): UDP socket should not be connected."; 475 if (socket_info.peer_address().IsSet() || 476 socket_info.peer_port().IsSet()) { 477 return "GetInfo(): unconnected UDP socket should not have peer " 478 "address or port."; 479 } 480 } 481 } 482 483 { 484 TestExtCompletionCallbackWithOutput<bool> callback(pp_instance()); 485 callback.WaitForResult(socket_.SetNoDelay( 486 socket_id, true, callback.GetCallback())); 487 if (callback.result() != PP_OK) 488 return "SetNoDelay(): failed."; 489 if (socket_type.value == socket::SocketType_Dev::TCP) { 490 if (!callback.output()) 491 return "SetNoDelay(): failed for TCP."; 492 } else { 493 if (callback.output()) 494 return "SetNoDelay(): did not fail for UDP."; 495 } 496 } 497 498 { 499 TestExtCompletionCallbackWithOutput<bool> callback(pp_instance()); 500 callback.WaitForResult(socket_.SetKeepAlive( 501 socket_id, true, 1000, callback.GetCallback())); 502 if (callback.result() != PP_OK) 503 return "SetKeepAlive(): failed."; 504 if (socket_type.value == socket::SocketType_Dev::TCP) { 505 if (!callback.output()) 506 return "SetKeepAlive(): failed for TCP."; 507 } else { 508 if (callback.output()) 509 return "SetKeepAlive(): did not fail for UDP."; 510 } 511 } 512 513 { 514 VarArrayBuffer input_array_buffer = ConvertToArrayBuffer(kSendContents); 515 size_t bytes_written = 0; 516 int32_t result_code = 0; 517 VarArrayBuffer data; 518 if (socket_type.value == socket::SocketType_Dev::TCP) { 519 TestExtCompletionCallbackWithOutput<socket::ReadInfo_Dev> 520 read_callback(pp_instance()); 521 int32_t read_result = socket_.Read(socket_id, Optional<int32_t>(), 522 read_callback.GetCallback()); 523 if (read_result != PP_OK_COMPLETIONPENDING) 524 return "Read(): did not wait for data."; 525 526 TestExtCompletionCallbackWithOutput<socket::WriteInfo_Dev> 527 write_callback(pp_instance()); 528 write_callback.WaitForResult(socket_.Write( 529 socket_id, input_array_buffer, write_callback.GetCallback())); 530 if (write_callback.result() != PP_OK) 531 return "Write(): failed."; 532 bytes_written = static_cast<size_t>( 533 write_callback.output().bytes_written()); 534 535 read_callback.WaitForResult(read_result); 536 if (read_callback.result() != PP_OK) 537 return "Read(): failed."; 538 socket::ReadInfo_Dev read_info = read_callback.output(); 539 result_code = read_info.result_code(), 540 data = read_info.data(); 541 } else { 542 TestExtCompletionCallbackWithOutput<socket::RecvFromInfo_Dev> 543 recv_from_callback(pp_instance()); 544 int32_t recv_from_result = socket_.RecvFrom( 545 socket_id, Optional<int32_t>(), recv_from_callback.GetCallback()); 546 if (recv_from_result != PP_OK_COMPLETIONPENDING) 547 return "RecvFrom(): did not wait for data."; 548 549 TestExtCompletionCallbackWithOutput<socket::WriteInfo_Dev> 550 send_to_callback(pp_instance()); 551 send_to_callback.WaitForResult(socket_.SendTo( 552 socket_id, input_array_buffer, address_, port_, 553 send_to_callback.GetCallback())); 554 if (send_to_callback.result() != PP_OK) 555 return "SendTo(): failed."; 556 bytes_written = static_cast<size_t>( 557 send_to_callback.output().bytes_written()); 558 559 recv_from_callback.WaitForResult(recv_from_result); 560 if (recv_from_callback.result() != PP_OK) 561 return "RecvFrom(): failed."; 562 socket::RecvFromInfo_Dev recv_from_info = recv_from_callback.output(); 563 result_code = recv_from_info.result_code(); 564 data = recv_from_info.data(); 565 } 566 567 if (bytes_written != strlen(kSendContents)) 568 return "SendTo() or Write(): did not send the whole data buffer."; 569 570 if (result_code > 0 && 571 static_cast<uint32_t>(result_code) != data.ByteLength()) { 572 return "Read() or RecvFrom(): inconsistent result code and byte " 573 "length."; 574 } 575 576 std::string output_string = ConvertFromArrayBuffer(&data); 577 size_t prefix_len = strlen(kReceiveContentsPrefix); 578 if (output_string.size() != prefix_len + kReceiveContentsSuffixSize || 579 output_string.compare(0, prefix_len, kReceiveContentsPrefix) != 0) { 580 return std::string("Read() or RecvFrom(): mismatched data: ").append( 581 output_string); 582 } 583 } 584 585 { 586 TestExtCompletionCallbackWithOutput< 587 std::vector<socket::NetworkInterface_Dev> > callback(pp_instance()); 588 callback.WaitForResult(socket_.GetNetworkList(callback.GetCallback())); 589 if (callback.result() != PP_OK) 590 return "GetNetworkList(): failed."; 591 if (callback.output().empty()) 592 return "GetNetworkList(): returned an empty list."; 593 } 594 595 socket_.Destroy(socket_id); 596 return std::string(); 597 } 598 599 void Log(PP_LogLevel level, const char* format, ...) { 600 va_list args; 601 va_start(args, format); 602 char buf[512]; 603 vsnprintf(buf, sizeof(buf) - 1, format, args); 604 buf[sizeof(buf) - 1] = '\0'; 605 va_end(args); 606 607 Var value(buf); 608 console_interface_->Log(pp_instance(), level, value.pp_var()); 609 } 610 611 void NotifyTestDone(const std::string& message) { 612 PostMessage(message); 613 } 614 615 VarArrayBuffer ConvertToArrayBuffer(const std::string data) { 616 VarArrayBuffer array_buffer(data.size()); 617 memcpy(array_buffer.Map(), data.c_str(), data.size()); 618 array_buffer.Unmap(); 619 return array_buffer; 620 } 621 622 std::string ConvertFromArrayBuffer(VarArrayBuffer* array_buffer) { 623 std::string result(static_cast<const char*>(array_buffer->Map()), 624 array_buffer->ByteLength()); 625 array_buffer->Unmap(); 626 return result; 627 } 628 629 socket::Socket_Dev socket_; 630 const PPB_Console* console_interface_; 631 632 std::string test_type_; 633 std::string address_; 634 int32_t port_; 635 }; 636 637 class MyModule : public Module { 638 public: 639 MyModule() : Module() {} 640 virtual ~MyModule() {} 641 642 virtual Instance* CreateInstance(PP_Instance instance) { 643 return new MyInstance(instance); 644 } 645 }; 646 647 namespace pp { 648 649 Module* CreateModule() { 650 return new MyModule(); 651 } 652 653 } // namespace pp 654 655