jspipe_test.cc revision f8ee788a64d60abd8f2d742a5fdedde054ecd910
1// Copyright 2014 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 <errno.h>
6#include <fcntl.h>
7#include <string.h>
8#include <sys/ioctl.h>
9#include <sys/select.h>
10#include <sys/stat.h>
11#include <sys/time.h>
12#include <string>
13
14#include "dev_fs_for_testing.h"
15#include "fake_ppapi/fake_messaging_interface.h"
16#include "gtest/gtest.h"
17#include "nacl_io/devfs/dev_fs.h"
18#include "nacl_io/filesystem.h"
19#include "nacl_io/ioctl.h"
20#include "nacl_io/kernel_intercept.h"
21#include "nacl_io/kernel_proxy.h"
22#include "nacl_io/osdirent.h"
23
24using namespace nacl_io;
25
26namespace {
27
28// Helper function for calling ki_ioctl without having
29// to construct a va_list.
30int ki_ioctl_wrapper(int fd, int request, ...) {
31  va_list ap;
32  va_start(ap, request);
33  int rtn = ki_ioctl(fd, request, ap);
34  va_end(ap);
35  return rtn;
36}
37
38// Helper function for converting PP_Var to C++ string
39std::string VarToString(VarInterface* var_iface, PP_Var var) {
40  EXPECT_EQ(PP_VARTYPE_STRING, var.type);
41  uint32_t len = 0;
42  const char* str = var_iface->VarToUtf8(var, &len);
43  return std::string(str, len);
44}
45
46PP_Var VarFromCStr(VarInterface* iface, const char* string) {
47  return iface->VarFromUtf8(string, strlen(string));
48}
49
50// Helper function for creating message in the format expected by jspipe
51// nodes: [ name, payload ]
52PP_Var CreatePipeMessage(PepperInterface* ppapi, const char* pipe,
53                         const char* operation, PP_Var payload) {
54  VarInterface* var_iface = ppapi->GetVarInterface();
55  VarDictionaryInterface* dict_iface = ppapi->GetVarDictionaryInterface();
56
57  // Create a two element array containing the name of the message
58  // as the first element.  Its up to the caller the then set the
59  // second array element.
60  PP_Var message = dict_iface->Create();
61  PP_Var pipe_var = VarFromCStr(var_iface, pipe);
62  PP_Var operation_var = VarFromCStr(var_iface, operation);
63  PP_Var pipe_key = VarFromCStr(var_iface, "pipe");
64  PP_Var payload_key = VarFromCStr(var_iface, "payload");
65  PP_Var operation_key = VarFromCStr(var_iface, "operation");
66  dict_iface->Set(message, pipe_key, pipe_var);
67  dict_iface->Set(message, operation_key, operation_var);
68  dict_iface->Set(message, payload_key, payload);
69  var_iface->Release(pipe_var);
70  var_iface->Release(operation_var);
71  var_iface->Release(payload);
72  var_iface->Release(pipe_key);
73  var_iface->Release(payload_key);
74  var_iface->Release(operation_key);
75  return message;
76}
77
78// Helper function for creating "ack" message in format expected
79// by jspipe nodes.
80PP_Var CreateAckMessage(PepperInterface* ppapi, const char* pipe,
81                        int32_t count) {
82  return CreatePipeMessage(ppapi, pipe, "ack", PP_MakeInt32(count));
83}
84
85// Helper function for creating "write" message in format expected
86// by jspipe nodes.
87PP_Var CreateWriteMessage(PepperInterface* ppapi,
88                          const char* pipe,
89                          const char* string,
90                          int length=-1) {
91  VarArrayBufferInterface* buffer_iface = ppapi->GetVarArrayBufferInterface();
92
93  if (length == -1)
94    length = strlen(string);
95
96  PP_Var buffer = buffer_iface->Create(length);
97  memcpy(buffer_iface->Map(buffer), string, length);
98  buffer_iface->Unmap(buffer);
99
100  return CreatePipeMessage(ppapi, pipe, "write", buffer);
101}
102
103class JSPipeTest : public ::testing::Test {
104 public:
105  void SetUp() {
106    ASSERT_EQ(0, ki_push_state_for_testing());
107    ASSERT_EQ(0, ki_init_interface(&kp_, &ppapi_));
108  }
109
110  void TearDown() {
111    ki_uninit();
112  }
113
114 protected:
115  FakePepperInterface ppapi_;
116  KernelProxy kp_;
117};
118
119class JSPipeNodeTest : public ::testing::Test {
120 public:
121  JSPipeNodeTest() : fs_(&ppapi_) {}
122
123  void SetUp() {
124    name_ = "jspipe1";
125    ASSERT_EQ(0, fs_.Access(Path("/jspipe1"), R_OK | W_OK));
126    ASSERT_EQ(EACCES, fs_.Access(Path("/jspipe1"), X_OK));
127    ASSERT_EQ(0, fs_.Open(Path("/jspipe1"), O_RDWR, &pipe_dev_));
128    ASSERT_NE(NULL_NODE, pipe_dev_.get());
129  }
130
131  /**
132   * Create a PP_Var message in the same way that we expect
133   * JavaScript code to, and send it to the pipe using ioctl()
134   */
135  int JSPipeInject(const char* string, int length=-1) {
136    PP_Var message = CreateWriteMessage(&ppapi_, name_, string, length);
137
138    // Send the message via ioctl
139    int rtn = pipe_dev_->Ioctl(NACL_IOC_HANDLEMESSAGE, &message);
140
141    // Release message
142    ppapi_.GetVarInterface()->Release(message);
143    return rtn;
144  }
145
146  int JSPipeInjectAck(int32_t count) {
147    PP_Var message = CreateAckMessage(&ppapi_, name_, count);
148
149    // Send the message via ioctl
150    int rtn = pipe_dev_->Ioctl(NACL_IOC_HANDLEMESSAGE, &message);
151
152    // Release message
153    ppapi_.GetVarInterface()->Release(message);
154    return rtn;
155  }
156
157  // Verify the contents of the jspipe mesage, which should be
158  // {
159  //   "pipe": '<pipe_name>',
160  //   "operation": '<command_name>',
161  //   "payload": payload
162  // }
163  void VerifyPipeMessage(PP_Var message,
164                         const char* pipe_name,
165                         const char* operation,
166                         const char* payload,
167                         int payload_length,
168                         int32_t int_payload=0) {
169    VarArrayInterface* array_iface = ppapi_.GetVarArrayInterface();
170    VarDictionaryInterface* dict_iface = ppapi_.GetVarDictionaryInterface();
171    VarInterface* var_iface = ppapi_.GetVarInterface();
172    VarArrayBufferInterface* buffer_iface = ppapi_.GetVarArrayBufferInterface();
173
174    // Verify we have a dictionary with 3 keys
175    ASSERT_EQ(PP_VARTYPE_DICTIONARY, message.type);
176    PP_Var keys = dict_iface->GetKeys(message);
177    ASSERT_EQ(PP_VARTYPE_ARRAY, keys.type);
178    ASSERT_EQ(3, array_iface->GetLength(keys));
179    var_iface->Release(keys);
180
181    // Verify the keys
182    PP_Var key1 = VarFromCStr(var_iface, "pipe");
183    PP_Var key2 = VarFromCStr(var_iface, "operation");
184    PP_Var key3 = VarFromCStr(var_iface, "payload");
185
186    // Verify pipe name and operation values
187    PP_Var value1 = dict_iface->Get(message, key1);
188    ASSERT_STREQ(pipe_name, VarToString(var_iface, value1).c_str());
189    var_iface->Release(value1);
190    var_iface->Release(key1);
191
192    PP_Var value2 = dict_iface->Get(message, key2);
193    ASSERT_STREQ(operation, VarToString(var_iface, value2).c_str());
194    var_iface->Release(value2);
195    var_iface->Release(key2);
196
197    // Verify the payload
198    PP_Var payload_var = dict_iface->Get(message, key3);
199    if (payload != NULL) {
200      ASSERT_EQ(PP_VARTYPE_ARRAY_BUFFER, payload_var.type);
201      ASSERT_EQ(0, memcmp(payload, buffer_iface->Map(payload_var),
202                          payload_length));
203    } else {
204      ASSERT_EQ(PP_VARTYPE_INT32, payload_var.type);
205      ASSERT_EQ(int_payload, payload_var.value.as_int);
206    }
207    var_iface->Release(key3);
208    var_iface->Release(payload_var);
209  }
210
211 protected:
212  FakePepperInterface ppapi_;
213  DevFsForTesting fs_;
214  ScopedNode pipe_dev_;
215  const char* name_;
216};
217
218TEST(JSPipeTestBasic, MissingPepper) {
219  // Create a devfs filesystem without giving it any Pepper implemenation.
220  TypedFsFactory<DevFs> factory;
221  ScopedFilesystem fs;
222  FsInitArgs args(1);
223  factory.CreateFilesystem(args, &fs);
224  ScopedNode pipe_dev;
225  ASSERT_EQ(0, fs->Open(Path("/jspipe1"), O_RDWR, &pipe_dev));
226
227  // Writing to a pipe should return EIO because Pepper is missing.
228  HandleAttr attrs;
229  int written = -1;
230  ASSERT_EQ(EIO, pipe_dev->Write(attrs, "test", 4, &written));
231}
232
233TEST_F(JSPipeNodeTest, InvalidIoctl) {
234  // 123 is not a valid ioctl request.
235  EXPECT_EQ(EINVAL, pipe_dev_->Ioctl(123));
236}
237
238TEST_F(JSPipeNodeTest, JSPipeInput) {
239  std::string message("hello, how are you?\n");
240
241  // First we send some data into the pipe.  This is how messages
242  // from javascript are injected into the pipe nodes.
243  ASSERT_EQ(0, JSPipeInject(message.c_str()));
244
245  // Now we make buffer we'll read into.
246  // We fill the buffer and a backup buffer with arbitrary data
247  // and compare them after reading to make sure read doesn't
248  // clobber parts of the buffer it shouldn't.
249  int bytes_read;
250  char buffer[100];
251  char backup_buffer[100];
252  memset(buffer, 'a', sizeof(buffer));
253  memset(backup_buffer, 'a', sizeof(backup_buffer));
254
255  // We read a small chunk first to ensure it doesn't give us
256  // more than we ask for.
257  HandleAttr attrs;
258  ASSERT_EQ(0, pipe_dev_->Read(attrs, buffer, 5, &bytes_read));
259  EXPECT_EQ(5, bytes_read);
260  EXPECT_EQ(0, memcmp(message.data(), buffer, 5));
261  EXPECT_EQ(0, memcmp(buffer + 5, backup_buffer + 5, sizeof(buffer)-5));
262
263  // Now we ask for more data than is left in the pipe, to ensure
264  // it doesn't give us more than there is.
265  ASSERT_EQ(0, pipe_dev_->Read(attrs, buffer + 5, sizeof(buffer)-5,
266                               &bytes_read));
267  EXPECT_EQ(bytes_read, message.size() - 5);
268  EXPECT_EQ(0, memcmp(message.data(), buffer, message.size()));
269  EXPECT_EQ(0, memcmp(buffer + message.size(),
270                      backup_buffer + message.size(),
271                      100 - message.size()));
272}
273
274TEST_F(JSPipeNodeTest, JSPipeOutput) {
275  std::string message("hello");
276
277  int bytes_written = 999;
278  HandleAttr attrs;
279  ASSERT_EQ(0, pipe_dev_->Write(attrs, message.c_str(), message.size(),
280                                &bytes_written));
281  ASSERT_EQ(message.size(), bytes_written);
282
283  FakeMessagingInterface* iface =
284      (FakeMessagingInterface*)ppapi_.GetMessagingInterface();
285
286  // Verify that exactly one message sent.
287  ASSERT_EQ(1, iface->messages.size());
288  PP_Var message_var = iface->messages[0];
289
290  // Verify the content of the message.
291  VerifyPipeMessage(message_var, "jspipe1", "write", message.c_str(),
292                    message.size());
293}
294
295TEST_F(JSPipeNodeTest, JSPipeOutputWithNulls) {
296  char message[20];
297  int message_len = sizeof(message);
298
299  // Construct a 20-byte  message containing the string 'hello' but with
300  // null chars on either end.
301  memset(message, 0 , message_len);
302  memcpy(message+10, "hello", 5);
303
304  int bytes_written = 999;
305  HandleAttr attrs;
306  EXPECT_EQ(0, pipe_dev_->Write(attrs, message, message_len, &bytes_written));
307  EXPECT_EQ(message_len, bytes_written);
308
309  // Verify that the correct messages was sent via PostMessage.
310  FakeMessagingInterface* iface =
311      (FakeMessagingInterface*)ppapi_.GetMessagingInterface();
312
313  // Verify that exaclty one message sent.
314  ASSERT_EQ(1, iface->messages.size());
315  PP_Var message_var = iface->messages[0];
316
317  // Verify the content of the message.
318  VerifyPipeMessage(message_var, "jspipe1", "write", message, message_len);
319}
320
321#define CHUNK_SIZE 678
322TEST_F(JSPipeNodeTest, JSPipeOutputBuffer) {
323  int ospace_orig = -1;
324  ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETOSPACE, &ospace_orig));
325  ASSERT_GT(ospace_orig, 0);
326
327  HandleAttr attrs;
328  attrs.flags = O_NONBLOCK;
329  char* message = (char*)malloc(CHUNK_SIZE);
330
331  // Keep writing data until we block.
332  int total_written = 0;
333  while (1) {
334    int bytes_written;
335    // Write some data
336    int rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written);
337    if (rtn != 0) {
338      ASSERT_EQ(EWOULDBLOCK, rtn);
339      int ospace = -1;
340      ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETOSPACE, &ospace));
341      ASSERT_EQ(0, ospace);
342      ASSERT_EQ(total_written, ospace_orig);
343      break;
344    }
345    total_written += bytes_written;
346  }
347
348  // At this point writes should always block
349  int bytes_written;
350  int rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written);
351  ASSERT_EQ(EWOULDBLOCK, rtn);
352
353  // Now inject and ACK message from JavaScript.
354  ASSERT_EQ(0, JSPipeInjectAck(10));
355
356  // Now it should be possible to write 10 bytes to the pipe.
357  rtn = pipe_dev_->Write(attrs, message, CHUNK_SIZE, &bytes_written);
358  ASSERT_EQ(0, rtn);
359  ASSERT_EQ(10, bytes_written);
360
361  free(message);
362}
363
364TEST_F(JSPipeNodeTest, JSPipeInputBuffer) {
365  char* message = (char*)malloc(CHUNK_SIZE);
366  memset(message, 1, CHUNK_SIZE);
367
368  int ispace_orig = -1;
369  ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETISPACE, &ispace_orig));
370
371  // Keep injecting data until the ioctl fails
372  int total_written = 0;
373  while (1) {
374    int rtn = JSPipeInject(message, CHUNK_SIZE);
375    if (rtn != 0) {
376      ASSERT_LT(total_written, ispace_orig);
377      ASSERT_GT(total_written, ispace_orig - CHUNK_SIZE - 1);
378      break;
379    }
380    total_written += CHUNK_SIZE;
381  }
382
383  int ispace = -1;
384  ASSERT_EQ(0, pipe_dev_->Ioctl(NACL_IOC_PIPE_GETISPACE, &ispace));
385  ASSERT_EQ(0, ispace);
386
387  // Check that no messages have thus far been sent to JavaScript
388  FakeMessagingInterface* iface =
389      (FakeMessagingInterface*)ppapi_.GetMessagingInterface();
390  ASSERT_EQ(0, iface->messages.size());
391
392  // Read some data from the pipe, which should trigger an ack message
393  int bytes_read = -1;
394  HandleAttr attrs;
395  ASSERT_EQ(0, pipe_dev_->Read(attrs, message, 5, &bytes_read));
396  ASSERT_EQ(5, bytes_read);
397
398  // Verify that an ack was sent to JavaScript
399  ASSERT_EQ(1, iface->messages.size());
400  PP_Var message_var = iface->messages[0];
401  VerifyPipeMessage(message_var, "jspipe1", "ack", NULL, 0, 5);
402
403  free(message);
404}
405
406// Returns:
407//   0 -> Not readable
408//   1 -> Readable
409//  -1 -> Error occured
410int IsReadable(int fd) {
411  struct timeval timeout = {0, 0};
412  fd_set readfds;
413  fd_set errorfds;
414  FD_ZERO(&readfds);
415  FD_ZERO(&errorfds);
416  FD_SET(fd, &readfds);
417  FD_SET(fd, &errorfds);
418  int rtn = ki_select(fd + 1, &readfds, NULL, &errorfds, &timeout);
419  if (rtn == 0)
420    return 0;  // not readable
421  if (rtn != 1)
422    return -1;  // error
423  if (FD_ISSET(fd, &errorfds))
424    return -2;  // error
425  if (!FD_ISSET(fd, &readfds))
426    return -3;  // error
427  return 1;     // readable
428}
429
430TEST_F(JSPipeTest, JSPipeSelect) {
431  struct timeval timeout;
432  fd_set readfds;
433  fd_set writefds;
434  fd_set errorfds;
435
436  int pipe_fd = ki_open("/dev/jspipe1", O_RDONLY);
437  ASSERT_GT(pipe_fd, 0) << "jspipe1 open failed: " << errno;
438
439  FD_ZERO(&readfds);
440  FD_ZERO(&errorfds);
441  FD_SET(pipe_fd, &readfds);
442  FD_SET(pipe_fd, &errorfds);
443  // 10 millisecond timeout
444  timeout.tv_sec = 0;
445  timeout.tv_usec = 10 * 1000;
446  // Should timeout when no input is available.
447  int rtn = ki_select(pipe_fd + 1, &readfds, NULL, &errorfds, &timeout);
448  ASSERT_EQ(0, rtn) << "select failed: " << rtn << " err=" << strerror(errno);
449  ASSERT_FALSE(FD_ISSET(pipe_fd, &readfds));
450  ASSERT_FALSE(FD_ISSET(pipe_fd, &errorfds));
451
452  FD_ZERO(&readfds);
453  FD_ZERO(&writefds);
454  FD_ZERO(&errorfds);
455  FD_SET(pipe_fd, &readfds);
456  FD_SET(pipe_fd, &writefds);
457  FD_SET(pipe_fd, &errorfds);
458  // Pipe should be writable on startup.
459  rtn = ki_select(pipe_fd + 1, &readfds, &writefds, &errorfds, NULL);
460  ASSERT_EQ(1, rtn);
461  ASSERT_TRUE(FD_ISSET(pipe_fd, &writefds));
462  ASSERT_FALSE(FD_ISSET(pipe_fd, &readfds));
463  ASSERT_FALSE(FD_ISSET(pipe_fd, &errorfds));
464
465  // Send 4 bytes to the pipe via ioctl
466  PP_Var message = CreateWriteMessage(&ppapi_, "jspipe1", "test");
467  ASSERT_EQ(0, ki_ioctl_wrapper(pipe_fd, NACL_IOC_HANDLEMESSAGE, &message));
468  ppapi_.GetVarInterface()->Release(message);
469
470  // Pipe should now be readable
471  ASSERT_EQ(1, IsReadable(pipe_fd));
472
473  ki_close(pipe_fd);
474}
475
476}
477