| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <sys/ioctl.h> |
| #include <sys/select.h> |
| #include <sys/stat.h> |
| #include <sys/time.h> |
| #include <string> |
| |
| #include "dev_fs_for_testing.h" |
| #include "fake_ppapi/fake_messaging_interface.h" |
| #include "gtest/gtest.h" |
| #include "nacl_io/devfs/dev_fs.h" |
| #include "nacl_io/filesystem.h" |
| #include "nacl_io/ioctl.h" |
| #include "nacl_io/kernel_intercept.h" |
| #include "nacl_io/kernel_proxy.h" |
| #include "nacl_io/osdirent.h" |
| |
| using namespace nacl_io; |
| |
| namespace { |
| |
| // Helper function for calling ki_ioctl without having |
| // to construct a va_list. |
| int ki_ioctl_wrapper(int fd, int request, ...) { |
| va_list ap; |
| va_start(ap, request); |
| int rtn = ki_ioctl(fd, request, ap); |
| va_end(ap); |
| return rtn; |
| } |
| |
| // Helper function for converting PP_Var to C++ string |
| std::string VarToString(VarInterface* var_iface, PP_Var var) { |
| EXPECT_EQ(PP_VARTYPE_STRING, var.type); |
| uint32_t len = 0; |
| const char* str = var_iface->VarToUtf8(var, &len); |
| return std::string(str, len); |
| } |
| |
| PP_Var VarFromCStr(VarInterface* iface, const char* string) { |
| return iface->VarFromUtf8(string, strlen(string)); |
| } |
| |
| // Helper function for creating message in the format expected by jspipe |
| // nodes: [ name, payload ] |
| PP_Var CreatePipeMessage(PepperInterface* ppapi, const char* pipe, |
| const char* operation, PP_Var payload) { |
| VarInterface* var_iface = ppapi->GetVarInterface(); |
| VarDictionaryInterface* dict_iface = ppapi->GetVarDictionaryInterface(); |
| |
| // Create a two element array containing the name of the message |
| // as the first element. Its up to the caller the then set the |
| // second array element. |
| PP_Var message = dict_iface->Create(); |
| PP_Var pipe_var = VarFromCStr(var_iface, pipe); |
| PP_Var operation_var = VarFromCStr(var_iface, operation); |
| PP_Var pipe_key = VarFromCStr(var_iface, "pipe"); |
| PP_Var payload_key = VarFromCStr(var_iface, "payload"); |
| PP_Var operation_key = VarFromCStr(var_iface, "operation"); |
| dict_iface->Set(message, pipe_key, pipe_var); |
| dict_iface->Set(message, operation_key, operation_var); |
| dict_iface->Set(message, payload_key, payload); |
| var_iface->Release(pipe_var); |
| var_iface->Release(operation_var); |
| var_iface->Release(payload); |
| var_iface->Release(pipe_key); |
| var_iface->Release(payload_key); |
| var_iface->Release(operation_key); |
| return message; |
| } |
| |
| // Helper function for creating "ack" message in format expected |
| // by jspipe nodes. |
| PP_Var CreateAckMessage(PepperInterface* ppapi, const char* pipe, |
| int32_t count) { |
| return CreatePipeMessage(ppapi, pipe, "ack", PP_MakeInt32(count)); |
| } |
| |
| // Helper function for creating "write" message in format expected |
| // by jspipe nodes. |
| PP_Var CreateWriteMessage(PepperInterface* ppapi, |
| const char* pipe, |
| const char* string, |
| int length=-1) { |
| VarArrayBufferInterface* buffer_iface = ppapi->GetVarArrayBufferInterface(); |
| |
| if (length == -1) |
| length = strlen(string); |
| |
| PP_Var buffer = buffer_iface->Create(length); |
| memcpy(buffer_iface->Map(buffer), string, length); |
| buffer_iface->Unmap(buffer); |
| |
| return CreatePipeMessage(ppapi, pipe, "write", buffer); |
| } |
| |
| class JSPipeTest : public ::testing::Test { |
| public: |
| void SetUp() { |
| ASSERT_EQ(0, ki_push_state_for_testing()); |
| ASSERT_EQ(0, ki_init_interface(&kp_, &ppapi_)); |
| } |
| |
| void TearDown() { |
| ki_uninit(); |
| } |
| |
| protected: |
| FakePepperInterface ppapi_; |
| KernelProxy kp_; |
| }; |
| |
| class JSPipeNodeTest : public ::testing::Test { |
| public: |
| JSPipeNodeTest() : fs_(&ppapi_) {} |
| |
| void SetUp() { |
| name_ = "jspipe1"; |
| ASSERT_EQ(0, fs_.Open(Path("/jspipe1"), O_RDWR, &pipe_dev_)); |
| ASSERT_NE(NULL_NODE, pipe_dev_.get()); |
| struct stat buf; |
| ASSERT_EQ(0, pipe_dev_->GetStat(&buf)); |
| ASSERT_EQ(S_IRUSR | S_IWUSR, buf.st_mode & S_IRWXU); |
| } |
| |
| /** |
| * Create a PP_Var message in the same way that we expect |
| * JavaScript code to, and send it to the pipe using ioctl() |
| */ |
| int JSPipeInject(const char* string, int length=-1) { |
| PP_Var message = CreateWriteMessage(&ppapi_, name_, string, length); |
| |
| // Send the message via ioctl |
| int rtn = pipe_dev_->Ioctl(NACL_IOC_HANDLEMESSAGE, &message); |
| |
| // Release message |
| ppapi_.GetVarInterface()->Release(message); |
| return rtn; |
| } |
| |
| int JSPipeInjectAck(int32_t count) { |
| PP_Var message = CreateAckMessage(&ppapi_, name_, count); |
| |
| // Send the message via ioctl |
| int rtn = pipe_dev_->Ioctl(NACL_IOC_HANDLEMESSAGE, &message); |
| |
| // Release message |
| ppapi_.GetVarInterface()->Release(message); |
| return rtn; |
| } |
| |
| // Verify the contents of the jspipe mesage, which should be |
| // { |
| // "pipe": '<pipe_name>', |
| // "operation": '<command_name>', |
| // "payload": payload |
| // } |
| void VerifyPipeMessage(PP_Var message, |
| const char* pipe_name, |
| const char* operation, |
| const char* payload, |
| int payload_length, |
| int32_t int_payload=0) { |
| VarArrayInterface* array_iface = ppapi_.GetVarArrayInterface(); |
| VarDictionaryInterface* dict_iface = ppapi_.GetVarDictionaryInterface(); |
| VarInterface* var_iface = ppapi_.GetVarInterface(); |
| VarArrayBufferInterface* buffer_iface = ppapi_.GetVarArrayBufferInterface(); |
| |
| // Verify we have a dictionary with 3 keys |
| ASSERT_EQ(PP_VARTYPE_DICTIONARY, message.type); |
| PP_Var keys = dict_iface->GetKeys(message); |
| ASSERT_EQ(PP_VARTYPE_ARRAY, keys.type); |
| ASSERT_EQ(3, array_iface->GetLength(keys)); |
| var_iface->Release(keys); |
| |
| // Verify the keys |
| PP_Var key1 = VarFromCStr(var_iface, "pipe"); |
| PP_Var key2 = VarFromCStr(var_iface, "operation"); |
| PP_Var key3 = VarFromCStr(var_iface, "payload"); |
| |
| // Verify pipe name and operation values |
| PP_Var value1 = dict_iface->Get(message, key1); |
| ASSERT_STREQ(pipe_name, VarToString(var_iface, value1).c_str()); |
| var_iface->Release(value1); |
| var_iface->Release(key1); |
| |
| PP_Var value2 = dict_iface->Get(message, key2); |
| ASSERT_STREQ(operation, VarToString(var_iface, value2).c_str()); |
| var_iface->Release(value2); |
| var_iface->Release(key2); |
| |
| // Verify the payload |
| PP_Var payload_var = dict_iface->Get(message, key3); |
| if (payload != NULL) { |
| ASSERT_EQ(PP_VARTYPE_ARRAY_BUFFER, payload_var.type); |
| ASSERT_EQ(0, memcmp(payload, buffer_iface->Map(payload_var), |
| payload_length)); |
| } else { |
| ASSERT_EQ(PP_VARTYPE_INT32, payload_var.type); |
| ASSERT_EQ(int_payload, payload_var.value.as_int); |
| } |
| var_iface->Release(key3); |
| var_iface->Release(payload_var); |
| } |
| |
| protected: |
| FakePepperInterface ppapi_; |
| DevFsForTesting fs_; |
| ScopedNode pipe_dev_; |
| const char* name_; |
| }; |
| |
| TEST(JSPipeTestBasic, MissingPepper) { |
| // Create a devfs filesystem without giving it any Pepper implemenation. |
| TypedFsFactory<DevFs> factory; |
| ScopedFilesystem fs; |
| FsInitArgs args(1); |
| factory.CreateFilesystem(args, &fs); |
| ScopedNode pipe_dev; |
| ASSERT_EQ(0, fs->Open(Path("/jspipe1"), O_RDWR, &pipe_dev)); |
| |
| // Writing to a pipe should return EIO because Pepper is missing. |
| HandleAttr attrs; |
| int written = -1; |
| ASSERT_EQ(EIO, pipe_dev->Write(attrs, "test", 4, &written)); |
| } |
| |
| TEST_F(JSPipeNodeTest, InvalidIoctl) { |
| // 123 is not a valid ioctl request. |
| EXPECT_EQ(EINVAL, pipe_dev_->Ioctl(123)); |
| } |
| |
| TEST_F(JSPipeNodeTest, JSPipeInput) { |
| std::string message("hello, how are you?\n"); |
| |
| // First we send some data into the pipe. This is how messages |
| // from javascript are injected into the pipe nodes. |
| ASSERT_EQ(0, JSPipeInject(message.c_str())); |
| |
| // Now we make buffer we'll read into. |
| // We fill the buffer and a backup buffer with arbitrary data |
| // and compare them after reading to make sure read doesn't |
| // clobber parts of the buffer it shouldn't. |
| int bytes_read; |
| char buffer[100]; |
| char backup_buffer[100]; |
| memset(buffer, 'a', sizeof(buffer)); |
| memset(backup_buffer, 'a', sizeof(backup_buffer)); |
| |
| // We read a small chunk first to ensure it doesn't give us |
| // more than we ask for. |
| HandleAttr attrs; |
| ASSERT_EQ(0, pipe_dev_->Read(attrs, buffer, 5, &bytes_read)); |
| EXPECT_EQ(5, bytes_read); |
| EXPECT_EQ(0, memcmp(message.data(), buffer, 5)); |
| EXPECT_EQ(0, memcmp(buffer + 5, backup_buffer + 5, sizeof(buffer)-5)); |
| |
| // Now we ask for more data than is left in the pipe, to ensure |
| // it doesn't give us more than there is. |
| ASSERT_EQ(0, pipe_dev_->Read(attrs, buffer + 5, sizeof(buffer)-5, |
| &bytes_read)); |
| EXPECT_EQ(bytes_read, message.size() - 5); |
| EXPECT_EQ(0, memcmp(message.data(), buffer, message.size())); |
| EXPECT_EQ(0, memcmp(buffer + message.size(), |
| backup_buffer + message.size(), |
| 100 - message.size())); |
| } |
| |
| TEST_F(JSPipeNodeTest, JSPipeOutput) { |
| std::string message("hello"); |
| |
| int bytes_written = 999; |
| HandleAttr attrs; |
| ASSERT_EQ(0, pipe_dev_->Write(attrs, message.c_str(), message.size(), |
| &bytes_written)); |
| ASSERT_EQ(message.size(), bytes_written); |
| |
| FakeMessagingInterface* iface = |
| (FakeMessagingInterface*)ppapi_.GetMessagingInterface(); |
| |
| // Verify that exactly one message sent. |
| ASSERT_EQ(1, iface->messages.size()); |
| PP_Var message_var = iface->messages[0]; |
| |
| // Verify the content of the message. |
| VerifyPipeMessage(message_var, "jspipe1", "write", message.c_str(), |
| message.size()); |
| } |
| |
| TEST_F(JSPipeNodeTest, JSPipeOutputWithNulls) { |
| char message[20]; |
| int message_len = sizeof(message); |
| |
| // Construct a 20-byte message containing the string 'hello' but with |
| // null chars on either end. |
| memset(message, 0 , message_len); |
| memcpy(message+10, "hello", 5); |
| |
| int bytes_written = 999; |
| HandleAttr attrs; |
| EXPECT_EQ(0, pipe_dev_->Write(attrs, message, message_len, &bytes_written)); |
| EXPECT_EQ(message_len, bytes_written); |
| |
| // Verify that the correct messages was sent via PostMessage. |
| FakeMessagingInterface* iface = |
| (FakeMessagingInterface*)ppapi_.GetMessagingInterface(); |
| |
| // Verify that exaclty one message sent. |
| ASSERT_EQ(1, iface->messages.size()); |
| PP_Var message_var = iface->messages[0]; |
| |
| // Verify the content of the message. |
| VerifyPipeMessage(message_var, "jspipe1", "write", message, message_len); |
| } |
| |
| #define CHUNK_SIZE 678 |
| TEST_F(JSPipeNodeTest, JSPipeOutputBuffer) { |
| int ospace_orig = -1; |
| ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETOSPACE, &ospace_orig)); |
| ASSERT_GT(ospace_orig, 0); |
| |
| HandleAttr attrs; |
| attrs.flags = O_NONBLOCK; |
| char* message = (char*)malloc(CHUNK_SIZE); |
| |
| // Keep writing data until we block. |
| int total_written = 0; |
| while (1) { |
| int bytes_written; |
| // Write some data |
| int rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written); |
| if (rtn != 0) { |
| ASSERT_EQ(EWOULDBLOCK, rtn); |
| int ospace = -1; |
| ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETOSPACE, &ospace)); |
| ASSERT_EQ(0, ospace); |
| ASSERT_EQ(total_written, ospace_orig); |
| break; |
| } |
| total_written += bytes_written; |
| } |
| |
| // At this point writes should always block |
| int bytes_written; |
| int rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written); |
| ASSERT_EQ(EWOULDBLOCK, rtn); |
| |
| // Now inject and ACK message from JavaScript. |
| ASSERT_EQ(0, JSPipeInjectAck(10)); |
| |
| // Now it should be possible to write 10 bytes to the pipe. |
| rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written); |
| ASSERT_EQ(0, rtn); |
| ASSERT_EQ(10, bytes_written); |
| |
| free(message); |
| } |
| |
| TEST_F(JSPipeNodeTest, JSPipeInputBuffer) { |
| char* message = (char*)malloc(CHUNK_SIZE); |
| memset(message, 1, CHUNK_SIZE); |
| |
| int ispace_orig = -1; |
| ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETISPACE, &ispace_orig)); |
| |
| // Keep injecting data until the ioctl fails |
| int total_written = 0; |
| while (1) { |
| int rtn = JSPipeInject(message, CHUNK_SIZE); |
| if (rtn != 0) { |
| ASSERT_LT(total_written, ispace_orig); |
| ASSERT_GT(total_written, ispace_orig - CHUNK_SIZE - 1); |
| break; |
| } |
| total_written += CHUNK_SIZE; |
| } |
| |
| int ispace = -1; |
| ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETISPACE, &ispace)); |
| ASSERT_EQ(0, ispace); |
| |
| // Check that no messages have thus far been sent to JavaScript |
| FakeMessagingInterface* iface = |
| (FakeMessagingInterface*)ppapi_.GetMessagingInterface(); |
| ASSERT_EQ(0, iface->messages.size()); |
| |
| // Read some data from the pipe, which should trigger an ack message |
| int bytes_read = -1; |
| HandleAttr attrs; |
| ASSERT_EQ(0, pipe_dev_->Read(attrs, message, 5, &bytes_read)); |
| ASSERT_EQ(5, bytes_read); |
| |
| // Verify that an ack was sent to JavaScript |
| ASSERT_EQ(1, iface->messages.size()); |
| PP_Var message_var = iface->messages[0]; |
| VerifyPipeMessage(message_var, "jspipe1", "ack", NULL, 0, 5); |
| |
| free(message); |
| } |
| |
| // Returns: |
| // 0 -> Not readable |
| // 1 -> Readable |
| // -1 -> Error occurred |
| int IsReadable(int fd) { |
| struct timeval timeout = {0, 0}; |
| fd_set readfds; |
| fd_set errorfds; |
| FD_ZERO(&readfds); |
| FD_ZERO(&errorfds); |
| FD_SET(fd, &readfds); |
| FD_SET(fd, &errorfds); |
| int rtn = ki_select(fd + 1, &readfds, NULL, &errorfds, &timeout); |
| if (rtn == 0) |
| return 0; // not readable |
| if (rtn != 1) |
| return -1; // error |
| if (FD_ISSET(fd, &errorfds)) |
| return -2; // error |
| if (!FD_ISSET(fd, &readfds)) |
| return -3; // error |
| return 1; // readable |
| } |
| |
| TEST_F(JSPipeTest, JSPipeSelect) { |
| struct timeval timeout; |
| fd_set readfds; |
| fd_set writefds; |
| fd_set errorfds; |
| |
| int pipe_fd = ki_open("/dev/jspipe1", O_RDONLY, 0); |
| ASSERT_GT(pipe_fd, 0) << "jspipe1 open failed: " << errno; |
| |
| FD_ZERO(&readfds); |
| FD_ZERO(&errorfds); |
| FD_SET(pipe_fd, &readfds); |
| FD_SET(pipe_fd, &errorfds); |
| // 10 millisecond timeout |
| timeout.tv_sec = 0; |
| timeout.tv_usec = 10 * 1000; |
| // Should timeout when no input is available. |
| int rtn = ki_select(pipe_fd + 1, &readfds, NULL, &errorfds, &timeout); |
| ASSERT_EQ(0, rtn) << "select failed: " << rtn << " err=" << strerror(errno); |
| ASSERT_FALSE(FD_ISSET(pipe_fd, &readfds)); |
| ASSERT_FALSE(FD_ISSET(pipe_fd, &errorfds)); |
| |
| FD_ZERO(&readfds); |
| FD_ZERO(&writefds); |
| FD_ZERO(&errorfds); |
| FD_SET(pipe_fd, &readfds); |
| FD_SET(pipe_fd, &writefds); |
| FD_SET(pipe_fd, &errorfds); |
| // Pipe should be writable on startup. |
| rtn = ki_select(pipe_fd + 1, &readfds, &writefds, &errorfds, NULL); |
| ASSERT_EQ(1, rtn); |
| ASSERT_TRUE(FD_ISSET(pipe_fd, &writefds)); |
| ASSERT_FALSE(FD_ISSET(pipe_fd, &readfds)); |
| ASSERT_FALSE(FD_ISSET(pipe_fd, &errorfds)); |
| |
| // Send 4 bytes to the pipe via ioctl |
| PP_Var message = CreateWriteMessage(&ppapi_, "jspipe1", "test"); |
| ASSERT_EQ(0, ki_ioctl_wrapper(pipe_fd, NACL_IOC_HANDLEMESSAGE, &message)); |
| ppapi_.GetVarInterface()->Release(message); |
| |
| // Pipe should now be readable |
| ASSERT_EQ(1, IsReadable(pipe_fd)); |
| |
| ki_close(pipe_fd); |
| } |
| |
| } |