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 "base/message_loop/message_loop.h" 6#include "ppapi/c/pp_errors.h" 7#include "ppapi/c/ppb_file_io.h" 8#include "ppapi/c/ppb_file_ref.h" 9#include "ppapi/c/ppb_file_system.h" 10#include "ppapi/proxy/file_system_resource.h" 11#include "ppapi/proxy/locking_resource_releaser.h" 12#include "ppapi/proxy/plugin_message_filter.h" 13#include "ppapi/proxy/ppapi_message_utils.h" 14#include "ppapi/proxy/ppapi_messages.h" 15#include "ppapi/proxy/ppapi_proxy_test.h" 16#include "ppapi/shared_impl/proxy_lock.h" 17#include "ppapi/shared_impl/scoped_pp_var.h" 18#include "ppapi/shared_impl/var.h" 19#include "ppapi/thunk/enter.h" 20#include "ppapi/thunk/ppb_file_system_api.h" 21#include "ppapi/thunk/thunk.h" 22 23using ppapi::proxy::ResourceMessageTestSink; 24using ppapi::thunk::EnterResource; 25using ppapi::thunk::PPB_FileSystem_API; 26 27namespace ppapi { 28namespace proxy { 29 30namespace { 31 32const int64_t kExpectedFileSystemSize = 100; 33const int64_t kQuotaRequestAmount1 = 10; 34const int64_t kQuotaRequestAmount2 = 20; 35 36class MockCompletionCallback { 37 public: 38 MockCompletionCallback() : called_(false) {} 39 40 bool called() { return called_; } 41 int32_t result() { return result_; } 42 43 static void Callback(void* user_data, int32_t result) { 44 MockCompletionCallback* that = 45 reinterpret_cast<MockCompletionCallback*>(user_data); 46 that->called_ = true; 47 that->result_ = result; 48 } 49 50 private: 51 bool called_; 52 int32_t result_; 53}; 54 55class MockRequestQuotaCallback { 56 public: 57 MockRequestQuotaCallback() : called_(false) {} 58 59 bool called() { return called_; } 60 int64_t result() { return result_; } 61 62 void Reset() { called_ = false; } 63 64 void Callback(int64_t result) { 65 ASSERT_FALSE(called_); 66 called_ = true; 67 result_ = result; 68 } 69 70 private: 71 bool called_; 72 int64_t result_; 73}; 74 75class FileSystemResourceTest : public PluginProxyTest { 76 public: 77 const PPB_FileSystem_1_0* file_system_iface; 78 const PPB_FileRef_1_1* file_ref_iface; 79 const PPB_FileIO_1_1* file_io_iface; 80 81 FileSystemResourceTest() 82 : file_system_iface(thunk::GetPPB_FileSystem_1_0_Thunk()), 83 file_ref_iface(thunk::GetPPB_FileRef_1_1_Thunk()), 84 file_io_iface(thunk::GetPPB_FileIO_1_1_Thunk()) { 85 } 86 87 void SendReply(const ResourceMessageCallParams& params, 88 int32_t result, 89 const IPC::Message& nested_message) { 90 ResourceMessageReplyParams reply_params(params.pp_resource(), 91 params.sequence()); 92 reply_params.set_result(result); 93 PluginMessageFilter::DispatchResourceReplyForTest( 94 reply_params, nested_message); 95 } 96 97 void SendOpenReply(const ResourceMessageCallParams& params, int32_t result) { 98 SendReply(params, result, PpapiPluginMsg_FileSystem_OpenReply()); 99 } 100 101 // Opens the given file system. 102 void OpenFileSystem(PP_Resource file_system) { 103 MockCompletionCallback cb; 104 int32_t result = file_system_iface->Open( 105 file_system, 106 kExpectedFileSystemSize, 107 PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); 108 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 109 110 // Should have sent two new "open" messages to the browser and renderer. 111 ResourceMessageTestSink::ResourceCallVector open_messages = 112 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); 113 ASSERT_EQ(2U, open_messages.size()); 114 sink().ClearMessages(); 115 116 // The resource is expecting two replies. 117 SendOpenReply(open_messages[0].first, PP_OK); 118 SendOpenReply(open_messages[1].first, PP_OK); 119 120 ASSERT_TRUE(cb.called()); 121 ASSERT_EQ(PP_OK, cb.result()); 122 } 123 124 // Opens the given file in the given file system. Since there is no host, 125 // the file handle will be invalid. 126 void OpenFile(PP_Resource file_io, 127 PP_Resource file_ref, 128 PP_Resource file_system) { 129 MockCompletionCallback cb; 130 int32_t result = file_io_iface->Open( 131 file_io, 132 file_ref, 133 PP_FILEOPENFLAG_WRITE, 134 PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); 135 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 136 137 // Should have sent an "open" message. 138 ResourceMessageCallParams params; 139 IPC::Message msg; 140 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 141 PpapiHostMsg_FileIO_Open::ID, ¶ms, &msg)); 142 sink().ClearMessages(); 143 144 // Send a success reply. 145 ResourceMessageReplyParams reply_params(params.pp_resource(), 146 params.sequence()); 147 reply_params.set_result(PP_OK); 148 PluginMessageFilter::DispatchResourceReplyForTest( 149 reply_params, 150 PpapiPluginMsg_FileIO_OpenReply(file_system, 151 0 /* max_written_offset */)); 152 } 153}; 154 155} // namespace 156 157// Test that Open fails if either host returns failure. The other tests exercise 158// the case where both hosts return PP_OK. 159TEST_F(FileSystemResourceTest, OpenFailure) { 160 // Fail if the first reply doesn't return PP_OK. 161 { 162 LockingResourceReleaser file_system( 163 file_system_iface->Create(pp_instance(), 164 PP_FILESYSTEMTYPE_LOCALTEMPORARY)); 165 166 MockCompletionCallback cb; 167 int32_t result = file_system_iface->Open( 168 file_system.get(), 169 kExpectedFileSystemSize, 170 PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); 171 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 172 173 ResourceMessageTestSink::ResourceCallVector open_messages = 174 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); 175 ASSERT_EQ(2U, open_messages.size()); 176 sink().ClearMessages(); 177 178 SendOpenReply(open_messages[0].first, PP_ERROR_FAILED); 179 SendOpenReply(open_messages[1].first, PP_OK); 180 181 ASSERT_TRUE(cb.called()); 182 ASSERT_EQ(PP_ERROR_FAILED, cb.result()); 183 } 184 // Fail if the second reply doesn't return PP_OK. 185 { 186 LockingResourceReleaser file_system( 187 file_system_iface->Create(pp_instance(), 188 PP_FILESYSTEMTYPE_LOCALTEMPORARY)); 189 190 MockCompletionCallback cb; 191 int32_t result = file_system_iface->Open( 192 file_system.get(), 193 kExpectedFileSystemSize, 194 PP_MakeCompletionCallback(&MockCompletionCallback::Callback, &cb)); 195 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 196 197 ResourceMessageTestSink::ResourceCallVector open_messages = 198 sink().GetAllResourceCallsMatching(PpapiHostMsg_FileSystem_Open::ID); 199 ASSERT_EQ(2U, open_messages.size()); 200 sink().ClearMessages(); 201 202 SendOpenReply(open_messages[0].first, PP_OK); 203 SendOpenReply(open_messages[1].first, PP_ERROR_FAILED); 204 205 ASSERT_TRUE(cb.called()); 206 ASSERT_EQ(PP_ERROR_FAILED, cb.result()); 207 } 208} 209 210TEST_F(FileSystemResourceTest, RequestQuota) { 211 LockingResourceReleaser file_system( 212 file_system_iface->Create(pp_instance(), 213 PP_FILESYSTEMTYPE_LOCALTEMPORARY)); 214 215 OpenFileSystem(file_system.get()); 216 217 // Create and open two files in the file system. FileIOResource calls 218 // FileSystemResource::OpenQuotaFile on success. 219 LockingResourceReleaser file_ref1( 220 file_ref_iface->Create(file_system.get(), "/file1")); 221 LockingResourceReleaser file_io1(file_io_iface->Create(pp_instance())); 222 OpenFile(file_io1.get(), file_ref1.get(), file_system.get()); 223 LockingResourceReleaser file_ref2( 224 file_ref_iface->Create(file_system.get(), "/file2")); 225 LockingResourceReleaser file_io2(file_io_iface->Create(pp_instance())); 226 OpenFile(file_io2.get(), file_ref2.get(), file_system.get()); 227 228 EnterResource<PPB_FileSystem_API> enter(file_system.get(), true); 229 ASSERT_FALSE(enter.failed()); 230 PPB_FileSystem_API* file_system_api = enter.object(); 231 232 MockRequestQuotaCallback cb1; 233 int64_t result = file_system_api->RequestQuota( 234 kQuotaRequestAmount1, 235 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); 236 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 237 238 // Should have sent a "reserve quota" message, with the amount of the request 239 // and a map of all currently open files to their max written offsets. 240 ResourceMessageCallParams params; 241 IPC::Message msg; 242 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 243 PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); 244 sink().ClearMessages(); 245 246 int64_t amount = 0; 247 FileGrowthMap file_growths; 248 ASSERT_TRUE(UnpackMessage<PpapiHostMsg_FileSystem_ReserveQuota>( 249 msg, &amount, &file_growths)); 250 ASSERT_EQ(kQuotaRequestAmount1, amount); 251 ASSERT_EQ(2U, file_growths.size()); 252 ASSERT_EQ(0, file_growths[file_io1.get()].max_written_offset); 253 ASSERT_EQ(0, file_growths[file_io2.get()].max_written_offset); 254 255 // Make another request while the "reserve quota" message is pending. 256 MockRequestQuotaCallback cb2; 257 result = file_system_api->RequestQuota( 258 kQuotaRequestAmount2, 259 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); 260 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 261 // No new "reserve quota" message should be sent while one is pending. 262 ASSERT_FALSE(sink().GetFirstResourceCallMatching( 263 PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); 264 { 265 ProxyAutoUnlock unlock_to_prevent_deadlock; 266 // Reply with quota reservation amount sufficient to cover both requests. 267 // Both callbacks should be called with the requests granted. 268 SendReply(params, 269 PP_OK, 270 PpapiPluginMsg_FileSystem_ReserveQuotaReply( 271 kQuotaRequestAmount1 + kQuotaRequestAmount2, 272 FileGrowthMapToFileSizeMapForTesting(file_growths))); 273 } 274 ASSERT_TRUE(cb1.called()); 275 ASSERT_EQ(kQuotaRequestAmount1, cb1.result()); 276 ASSERT_TRUE(cb2.called()); 277 ASSERT_EQ(kQuotaRequestAmount2, cb2.result()); 278 cb1.Reset(); 279 cb2.Reset(); 280 281 // All requests should fail when insufficient quota is returned to satisfy 282 // the first request. 283 result = file_system_api->RequestQuota( 284 kQuotaRequestAmount1, 285 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); 286 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 287 result = file_system_api->RequestQuota( 288 kQuotaRequestAmount2, 289 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); 290 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 291 292 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 293 PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); 294 sink().ClearMessages(); 295 { 296 ProxyAutoUnlock unlock_to_prevent_deadlock; 297 // Reply with quota reservation amount insufficient to cover the first 298 // request. 299 SendReply(params, 300 PP_OK, 301 PpapiPluginMsg_FileSystem_ReserveQuotaReply( 302 kQuotaRequestAmount1 - 1, 303 FileGrowthMapToFileSizeMapForTesting(file_growths))); 304 } 305 ASSERT_TRUE(cb1.called()); 306 ASSERT_EQ(0, cb1.result()); 307 ASSERT_TRUE(cb2.called()); 308 ASSERT_EQ(0, cb2.result()); 309 cb1.Reset(); 310 cb2.Reset(); 311 312 // A new request should be made if the quota reservation is enough to satisfy 313 // at least one request. 314 result = file_system_api->RequestQuota( 315 kQuotaRequestAmount1, 316 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); 317 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 318 result = file_system_api->RequestQuota( 319 kQuotaRequestAmount2, 320 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb2))); 321 ASSERT_EQ(PP_OK_COMPLETIONPENDING, result); 322 323 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 324 PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); 325 sink().ClearMessages(); 326 { 327 ProxyAutoUnlock unlock_to_prevent_deadlock; 328 // Reply with quota reservation amount sufficient only to cover the first 329 // request. 330 SendReply(params, 331 PP_OK, 332 PpapiPluginMsg_FileSystem_ReserveQuotaReply( 333 kQuotaRequestAmount1, 334 FileGrowthMapToFileSizeMapForTesting(file_growths))); 335 } 336 ASSERT_TRUE(cb1.called()); 337 ASSERT_EQ(kQuotaRequestAmount1, cb1.result()); 338 ASSERT_FALSE(cb2.called()); 339 340 // Another request message should have been sent. 341 ASSERT_TRUE(sink().GetFirstResourceCallMatching( 342 PpapiHostMsg_FileSystem_ReserveQuota::ID, ¶ms, &msg)); 343 sink().ClearMessages(); 344 { 345 ProxyAutoUnlock unlock_to_prevent_deadlock; 346 // Reply with quota reservation amount sufficient to cover the second 347 // request and some extra. 348 SendReply(params, 349 PP_OK, 350 PpapiPluginMsg_FileSystem_ReserveQuotaReply( 351 kQuotaRequestAmount1 + kQuotaRequestAmount2, 352 FileGrowthMapToFileSizeMapForTesting(file_growths))); 353 } 354 355 ASSERT_TRUE(cb2.called()); 356 ASSERT_EQ(kQuotaRequestAmount2, cb2.result()); 357 cb1.Reset(); 358 cb2.Reset(); 359 360 // There is kQuotaRequestAmount1 of quota left, and a request for it should 361 // succeed immediately. 362 result = file_system_api->RequestQuota( 363 kQuotaRequestAmount1, 364 base::Bind(&MockRequestQuotaCallback::Callback, base::Unretained(&cb1))); 365 ASSERT_EQ(kQuotaRequestAmount1, result); 366} 367 368} // namespace proxy 369} // namespace ppapi 370