1 // Copyright 2017 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 "content/browser/media/cdm_storage_impl.h"
6 
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/files/file.h"
10 #include "base/logging.h"
11 #include "base/run_loop.h"
12 #include "content/public/browser/render_frame_host.h"
13 #include "content/public/browser/web_contents.h"
14 #include "content/public/test/navigation_simulator.h"
15 #include "content/public/test/test_renderer_host.h"
16 #include "media/mojo/mojom/cdm_storage.mojom.h"
17 #include "mojo/public/cpp/bindings/associated_remote.h"
18 #include "mojo/public/cpp/bindings/pending_associated_remote.h"
19 #include "mojo/public/cpp/bindings/remote.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21 #include "url/gurl.h"
22 #include "url/origin.h"
23 
24 using media::mojom::CdmFile;
25 using media::mojom::CdmStorage;
26 
27 namespace content {
28 
29 namespace {
30 
31 const char kTestFileSystemId[] = "test_file_system";
32 const char kTestOrigin[] = "http://www.test.com";
33 
34 // Helper functions to manipulate RenderFrameHosts.
35 
SimulateNavigation(RenderFrameHost ** rfh,const GURL & url)36 void SimulateNavigation(RenderFrameHost** rfh, const GURL& url) {
37   auto navigation_simulator =
38       NavigationSimulator::CreateRendererInitiated(url, *rfh);
39   navigation_simulator->Commit();
40   *rfh = navigation_simulator->GetFinalRenderFrameHost();
41 }
42 
43 // Helper that wraps a base::RunLoop and only quits the RunLoop
44 // if the expected number of quit calls have happened.
45 class RunLoopWithExpectedCount {
46  public:
47   RunLoopWithExpectedCount() = default;
~RunLoopWithExpectedCount()48   ~RunLoopWithExpectedCount() { DCHECK_EQ(0, remaining_quit_calls_); }
49 
Run(int expected_quit_calls)50   void Run(int expected_quit_calls) {
51     DCHECK_GT(expected_quit_calls, 0);
52     DCHECK_EQ(remaining_quit_calls_, 0);
53     remaining_quit_calls_ = expected_quit_calls;
54     run_loop_.reset(new base::RunLoop());
55     run_loop_->Run();
56   }
57 
Quit()58   void Quit() {
59     if (--remaining_quit_calls_ > 0)
60       return;
61     run_loop_->Quit();
62   }
63 
64  private:
65   std::unique_ptr<base::RunLoop> run_loop_;
66   int remaining_quit_calls_ = 0;
67 
68   DISALLOW_COPY_AND_ASSIGN(RunLoopWithExpectedCount);
69 };
70 
71 }  // namespace
72 
73 class CdmStorageTest : public RenderViewHostTestHarness {
74  public:
CdmStorageTest()75   CdmStorageTest()
76       : RenderViewHostTestHarness(
77             content::BrowserTaskEnvironment::REAL_IO_THREAD) {}
78 
79  protected:
SetUp()80   void SetUp() final {
81     RenderViewHostTestHarness::SetUp();
82     rfh_ = web_contents()->GetMainFrame();
83     RenderFrameHostTester::For(rfh_)->InitializeRenderFrameIfNeeded();
84     SimulateNavigation(&rfh_, GURL(kTestOrigin));
85   }
86 
87   // Creates and initializes the CdmStorage object using |file_system_id|.
88   // Returns true if successful, false otherwise.
Initialize(const std::string & file_system_id)89   void Initialize(const std::string& file_system_id) {
90     DVLOG(3) << __func__;
91 
92     // Create the CdmStorageImpl object. |cdm_storage_| will own the resulting
93     // object.
94     CdmStorageImpl::Create(rfh_, file_system_id,
95                            cdm_storage_.BindNewPipeAndPassReceiver());
96   }
97 
98   // Open the file |name|. Returns true if the file returned is valid, false
99   // otherwise. On success |cdm_file| is bound to the CdmFileImpl object.
Open(const std::string & name,mojo::AssociatedRemote<CdmFile> * cdm_file)100   bool Open(const std::string& name,
101             mojo::AssociatedRemote<CdmFile>* cdm_file) {
102     DVLOG(3) << __func__;
103 
104     CdmStorage::Status status;
105     cdm_storage_->Open(
106         name, base::BindOnce(&CdmStorageTest::OpenDone, base::Unretained(this),
107                              &status, cdm_file));
108     RunAndWaitForResult(1);
109     return status == CdmStorage::Status::kSuccess;
110   }
111 
112   // Reads the contents of the previously opened |cdm_file|. If successful,
113   // true is returned and |data| is updated with the contents of the file.
114   // If unable to read the file, false is returned.
Read(CdmFile * cdm_file,std::vector<uint8_t> * data)115   bool Read(CdmFile* cdm_file, std::vector<uint8_t>* data) {
116     DVLOG(3) << __func__;
117 
118     CdmFile::Status status;
119     cdm_file->Read(base::BindOnce(&CdmStorageTest::FileRead,
120                                   base::Unretained(this), &status, data));
121     RunAndWaitForResult(1);
122     return status == CdmFile::Status::kSuccess;
123   }
124 
125   // Attempts to reads the contents of the previously opened |cdm_file| twice.
126   // We don't really care about the data, just that 1 read succeeds and the
127   // other fails.
ReadTwice(CdmFile * cdm_file,CdmFile::Status * status1,CdmFile::Status * status2)128   void ReadTwice(CdmFile* cdm_file,
129                  CdmFile::Status* status1,
130                  CdmFile::Status* status2) {
131     DVLOG(3) << __func__;
132     std::vector<uint8_t> data1;
133     std::vector<uint8_t> data2;
134 
135     cdm_file->Read(base::BindOnce(&CdmStorageTest::FileRead,
136                                   base::Unretained(this), status1, &data1));
137     cdm_file->Read(base::BindOnce(&CdmStorageTest::FileRead,
138                                   base::Unretained(this), status2, &data2));
139     RunAndWaitForResult(2);
140   }
141 
142   // Writes |data| to the previously opened |cdm_file|, replacing the contents
143   // of the file. Returns true if successful, false otherwise.
Write(CdmFile * cdm_file,const std::vector<uint8_t> & data)144   bool Write(CdmFile* cdm_file, const std::vector<uint8_t>& data) {
145     DVLOG(3) << __func__;
146 
147     CdmFile::Status status;
148     cdm_file->Write(data, base::BindOnce(&CdmStorageTest::FileWritten,
149                                          base::Unretained(this), &status));
150     RunAndWaitForResult(1);
151     return status == CdmFile::Status::kSuccess;
152   }
153 
154   // Attempts to write the contents of the previously opened |cdm_file| twice.
155   // We don't really care about the data, just that 1 read succeeds and the
156   // other fails.
WriteTwice(CdmFile * cdm_file,CdmFile::Status * status1,CdmFile::Status * status2)157   void WriteTwice(CdmFile* cdm_file,
158                   CdmFile::Status* status1,
159                   CdmFile::Status* status2) {
160     DVLOG(3) << __func__;
161 
162     cdm_file->Write({1, 2, 3}, base::BindOnce(&CdmStorageTest::FileWritten,
163                                               base::Unretained(this), status1));
164     cdm_file->Write({4, 5, 6}, base::BindOnce(&CdmStorageTest::FileWritten,
165                                               base::Unretained(this), status2));
166     RunAndWaitForResult(2);
167   }
168 
169  private:
OpenDone(CdmStorage::Status * status,mojo::AssociatedRemote<CdmFile> * cdm_file,CdmStorage::Status actual_status,mojo::PendingAssociatedRemote<CdmFile> actual_cdm_file)170   void OpenDone(CdmStorage::Status* status,
171                 mojo::AssociatedRemote<CdmFile>* cdm_file,
172                 CdmStorage::Status actual_status,
173                 mojo::PendingAssociatedRemote<CdmFile> actual_cdm_file) {
174     DVLOG(3) << __func__;
175     *status = actual_status;
176 
177     if (!actual_cdm_file) {
178       run_loop_with_count_->Quit();
179       return;
180     }
181     // Open() returns a mojo::PendingAssociatedRemote<CdmFile>, so bind it to
182     // the mojo::AssociatedRemote<CdmFileAssociated> provided.
183     mojo::AssociatedRemote<CdmFile> cdm_file_remote;
184     cdm_file_remote.Bind(std::move(actual_cdm_file));
185     *cdm_file = std::move(cdm_file_remote);
186     run_loop_with_count_->Quit();
187   }
188 
FileRead(CdmFile::Status * status,std::vector<uint8_t> * data,CdmFile::Status actual_status,const std::vector<uint8_t> & actual_data)189   void FileRead(CdmFile::Status* status,
190                 std::vector<uint8_t>* data,
191                 CdmFile::Status actual_status,
192                 const std::vector<uint8_t>& actual_data) {
193     DVLOG(3) << __func__;
194     *status = actual_status;
195     *data = actual_data;
196     run_loop_with_count_->Quit();
197   }
198 
FileWritten(CdmFile::Status * status,CdmFile::Status actual_status)199   void FileWritten(CdmFile::Status* status, CdmFile::Status actual_status) {
200     DVLOG(3) << __func__;
201     *status = actual_status;
202     run_loop_with_count_->Quit();
203   }
204 
205   // Start running and allow the asynchronous IO operations to complete.
RunAndWaitForResult(int expected_quit_calls)206   void RunAndWaitForResult(int expected_quit_calls) {
207     run_loop_with_count_ = std::make_unique<RunLoopWithExpectedCount>();
208     run_loop_with_count_->Run(expected_quit_calls);
209   }
210 
211   RenderFrameHost* rfh_ = nullptr;
212   mojo::Remote<CdmStorage> cdm_storage_;
213   std::unique_ptr<RunLoopWithExpectedCount> run_loop_with_count_;
214 };
215 
TEST_F(CdmStorageTest,InvalidFileSystemIdWithSlash)216 TEST_F(CdmStorageTest, InvalidFileSystemIdWithSlash) {
217   Initialize("name/");
218 
219   const char kFileName[] = "valid_file_name";
220   mojo::AssociatedRemote<CdmFile> cdm_file;
221   EXPECT_FALSE(Open(kFileName, &cdm_file));
222   EXPECT_FALSE(cdm_file.is_bound());
223 }
224 
TEST_F(CdmStorageTest,InvalidFileSystemIdWithBackSlash)225 TEST_F(CdmStorageTest, InvalidFileSystemIdWithBackSlash) {
226   Initialize("name\\");
227 
228   const char kFileName[] = "valid_file_name";
229   mojo::AssociatedRemote<CdmFile> cdm_file;
230   EXPECT_FALSE(Open(kFileName, &cdm_file));
231   EXPECT_FALSE(cdm_file.is_bound());
232 }
233 
TEST_F(CdmStorageTest,InvalidFileSystemIdEmpty)234 TEST_F(CdmStorageTest, InvalidFileSystemIdEmpty) {
235   Initialize("");
236 
237   const char kFileName[] = "valid_file_name";
238   mojo::AssociatedRemote<CdmFile> cdm_file;
239   EXPECT_FALSE(Open(kFileName, &cdm_file));
240   EXPECT_FALSE(cdm_file.is_bound());
241 }
242 
TEST_F(CdmStorageTest,InvalidFileName)243 TEST_F(CdmStorageTest, InvalidFileName) {
244   Initialize(kTestFileSystemId);
245 
246   // Anything other than ASCII letter, digits, and -._ will fail. Add a
247   // Unicode character to the name.
248   const char kFileName[] = "openfile\u1234";
249   mojo::AssociatedRemote<CdmFile> cdm_file;
250   EXPECT_FALSE(Open(kFileName, &cdm_file));
251   EXPECT_FALSE(cdm_file.is_bound());
252 }
253 
TEST_F(CdmStorageTest,InvalidFileNameEmpty)254 TEST_F(CdmStorageTest, InvalidFileNameEmpty) {
255   Initialize(kTestFileSystemId);
256 
257   const char kFileName[] = "";
258   mojo::AssociatedRemote<CdmFile> cdm_file;
259   EXPECT_FALSE(Open(kFileName, &cdm_file));
260   EXPECT_FALSE(cdm_file.is_bound());
261 }
262 
TEST_F(CdmStorageTest,InvalidFileNameStartWithUnderscore)263 TEST_F(CdmStorageTest, InvalidFileNameStartWithUnderscore) {
264   Initialize(kTestFileSystemId);
265 
266   const char kFileName[] = "_invalid";
267   mojo::AssociatedRemote<CdmFile> cdm_file;
268   EXPECT_FALSE(Open(kFileName, &cdm_file));
269   EXPECT_FALSE(cdm_file.is_bound());
270 }
271 
TEST_F(CdmStorageTest,InvalidFileNameTooLong)272 TEST_F(CdmStorageTest, InvalidFileNameTooLong) {
273   Initialize(kTestFileSystemId);
274 
275   // Limit is 256 characters, so try a file name with 257.
276   const std::string kFileName(257, 'a');
277   mojo::AssociatedRemote<CdmFile> cdm_file;
278   EXPECT_FALSE(Open(kFileName, &cdm_file));
279   EXPECT_FALSE(cdm_file.is_bound());
280 }
281 
TEST_F(CdmStorageTest,OpenFile)282 TEST_F(CdmStorageTest, OpenFile) {
283   Initialize(kTestFileSystemId);
284 
285   const char kFileName[] = "test_file_name";
286   mojo::AssociatedRemote<CdmFile> cdm_file;
287   EXPECT_TRUE(Open(kFileName, &cdm_file));
288   EXPECT_TRUE(cdm_file.is_bound());
289 }
290 
TEST_F(CdmStorageTest,OpenFileLocked)291 TEST_F(CdmStorageTest, OpenFileLocked) {
292   Initialize(kTestFileSystemId);
293 
294   const char kFileName[] = "test_file_name";
295   mojo::AssociatedRemote<CdmFile> cdm_file1;
296   EXPECT_TRUE(Open(kFileName, &cdm_file1));
297   EXPECT_TRUE(cdm_file1.is_bound());
298 
299   // Second attempt on the same file should fail as the file is locked.
300   mojo::AssociatedRemote<CdmFile> cdm_file2;
301   EXPECT_FALSE(Open(kFileName, &cdm_file2));
302   EXPECT_FALSE(cdm_file2.is_bound());
303 
304   // Now close the first file and try again. It should be free now.
305   cdm_file1.reset();
306 
307   mojo::AssociatedRemote<CdmFile> cdm_file3;
308   EXPECT_TRUE(Open(kFileName, &cdm_file3));
309   EXPECT_TRUE(cdm_file3.is_bound());
310 }
311 
TEST_F(CdmStorageTest,MultipleFiles)312 TEST_F(CdmStorageTest, MultipleFiles) {
313   Initialize(kTestFileSystemId);
314 
315   const char kFileName1[] = "file1";
316   mojo::AssociatedRemote<CdmFile> cdm_file1;
317   EXPECT_TRUE(Open(kFileName1, &cdm_file1));
318   EXPECT_TRUE(cdm_file1.is_bound());
319 
320   const char kFileName2[] = "file2";
321   mojo::AssociatedRemote<CdmFile> cdm_file2;
322   EXPECT_TRUE(Open(kFileName2, &cdm_file2));
323   EXPECT_TRUE(cdm_file2.is_bound());
324 
325   const char kFileName3[] = "file3";
326   mojo::AssociatedRemote<CdmFile> cdm_file3;
327   EXPECT_TRUE(Open(kFileName3, &cdm_file3));
328   EXPECT_TRUE(cdm_file3.is_bound());
329 }
330 
TEST_F(CdmStorageTest,WriteThenReadFile)331 TEST_F(CdmStorageTest, WriteThenReadFile) {
332   Initialize(kTestFileSystemId);
333 
334   const char kFileName[] = "test_file_name";
335   mojo::AssociatedRemote<CdmFile> cdm_file;
336   EXPECT_TRUE(Open(kFileName, &cdm_file));
337   EXPECT_TRUE(cdm_file.is_bound());
338 
339   // Write several bytes and read them back.
340   std::vector<uint8_t> kTestData = {'r', 'a', 'n', 'd', 'o', 'm'};
341   EXPECT_TRUE(Write(cdm_file.get(), kTestData));
342 
343   std::vector<uint8_t> data_read;
344   EXPECT_TRUE(Read(cdm_file.get(), &data_read));
345   EXPECT_EQ(kTestData, data_read);
346 }
347 
TEST_F(CdmStorageTest,ReadThenWriteEmptyFile)348 TEST_F(CdmStorageTest, ReadThenWriteEmptyFile) {
349   Initialize(kTestFileSystemId);
350 
351   const char kFileName[] = "empty_file_name";
352   mojo::AssociatedRemote<CdmFile> cdm_file;
353   EXPECT_TRUE(Open(kFileName, &cdm_file));
354   EXPECT_TRUE(cdm_file.is_bound());
355 
356   // New file should be empty.
357   std::vector<uint8_t> data_read;
358   EXPECT_TRUE(Read(cdm_file.get(), &data_read));
359   EXPECT_EQ(0u, data_read.size());
360 
361   // Write nothing.
362   EXPECT_TRUE(Write(cdm_file.get(), std::vector<uint8_t>()));
363 
364   // Should still be empty.
365   EXPECT_TRUE(Read(cdm_file.get(), &data_read));
366   EXPECT_EQ(0u, data_read.size());
367 }
368 
TEST_F(CdmStorageTest,ParallelRead)369 TEST_F(CdmStorageTest, ParallelRead) {
370   Initialize(kTestFileSystemId);
371 
372   const char kFileName[] = "duplicate_read_file_name";
373   mojo::AssociatedRemote<CdmFile> cdm_file;
374   EXPECT_TRUE(Open(kFileName, &cdm_file));
375   EXPECT_TRUE(cdm_file.is_bound());
376 
377   CdmFile::Status status1;
378   CdmFile::Status status2;
379   ReadTwice(cdm_file.get(), &status1, &status2);
380 
381   // One call should succeed, one should fail.
382   EXPECT_TRUE((status1 == CdmFile::Status::kSuccess &&
383                status2 == CdmFile::Status::kFailure) ||
384               (status1 == CdmFile::Status::kFailure &&
385                status2 == CdmFile::Status::kSuccess));
386 }
387 
TEST_F(CdmStorageTest,ParallelWrite)388 TEST_F(CdmStorageTest, ParallelWrite) {
389   Initialize(kTestFileSystemId);
390 
391   const char kFileName[] = "duplicate_write_file_name";
392   mojo::AssociatedRemote<CdmFile> cdm_file;
393   EXPECT_TRUE(Open(kFileName, &cdm_file));
394   EXPECT_TRUE(cdm_file.is_bound());
395 
396   CdmFile::Status status1;
397   CdmFile::Status status2;
398   WriteTwice(cdm_file.get(), &status1, &status2);
399 
400   // One call should succeed, one should fail.
401   EXPECT_TRUE((status1 == CdmFile::Status::kSuccess &&
402                status2 == CdmFile::Status::kFailure) ||
403               (status1 == CdmFile::Status::kFailure &&
404                status2 == CdmFile::Status::kSuccess));
405 }
406 
407 }  // namespace content
408