1// Copyright 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 "media/cdm/ppapi/cdm_file_io_impl.h"
6
7#include <algorithm>
8#include <sstream>
9
10#include "media/cdm/ppapi/cdm_logging.h"
11#include "ppapi/c/pp_errors.h"
12#include "ppapi/cpp/dev/url_util_dev.h"
13
14namespace media {
15
16const int kReadSize = 4 * 1024;  // Arbitrary choice.
17
18// Call func_call and check the result. If the result is not
19// PP_OK_COMPLETIONPENDING, print out logs, call OnError() and return.
20#define CHECK_PP_OK_COMPLETIONPENDING(func_call, error_type)            \
21  do {                                                                  \
22    int32_t result = func_call;                                         \
23    PP_DCHECK(result != PP_OK);                                         \
24    if (result != PP_OK_COMPLETIONPENDING) {                            \
25      CDM_DLOG() << #func_call << " failed with result: " << result;    \
26      OnError(error_type);                                              \
27      return;                                                           \
28    }                                                                   \
29  } while (0)
30
31#if !defined(NDEBUG)
32// PPAPI calls should only be made on the main thread. In this file, main thread
33// checking is only performed in public APIs and the completion callbacks. This
34// ensures all functions are running on the main thread since internal methods
35// are called either by the public APIs or by the completion callbacks.
36static bool IsMainThread() {
37  return pp::Module::Get()->core()->IsMainThread();
38}
39#endif  // !defined(NDEBUG)
40
41// Posts a task to run |cb| on the main thread. The task is posted even if the
42// current thread is the main thread.
43static void PostOnMain(pp::CompletionCallback cb) {
44  pp::Module::Get()->core()->CallOnMainThread(0, cb, PP_OK);
45}
46
47CdmFileIOImpl::FileLockMap* CdmFileIOImpl::file_lock_map_ = NULL;
48
49CdmFileIOImpl::ResourceTracker::ResourceTracker() {
50  // Do nothing here since we lazy-initialize CdmFileIOImpl::file_lock_map_
51  // in CdmFileIOImpl::AcquireFileLock().
52}
53
54CdmFileIOImpl::ResourceTracker::~ResourceTracker() {
55  delete CdmFileIOImpl::file_lock_map_;
56}
57
58CdmFileIOImpl::CdmFileIOImpl(cdm::FileIOClient* client, PP_Instance pp_instance)
59    : state_(FILE_UNOPENED),
60      client_(client),
61      pp_instance_handle_(pp_instance),
62      callback_factory_(this),
63      io_offset_(0) {
64  PP_DCHECK(IsMainThread());
65  PP_DCHECK(pp_instance);  // 0 indicates a "NULL handle".
66}
67
68CdmFileIOImpl::~CdmFileIOImpl() {
69  PP_DCHECK(state_ == FILE_CLOSED);
70}
71
72// Call sequence: Open() -> OpenFileSystem() -> OpenFile() -> FILE_OPENED.
73void CdmFileIOImpl::Open(const char* file_name, uint32_t file_name_size) {
74  CDM_DLOG() << __FUNCTION__;
75  PP_DCHECK(IsMainThread());
76
77  if (state_ != FILE_UNOPENED) {
78    CDM_DLOG() << "Open() called in an invalid state.";
79    OnError(OPEN_ERROR);
80    return;
81  }
82
83  // File name should not contain any path separators.
84  std::string file_name_str(file_name, file_name_size);
85  if (file_name_str.find('/') != std::string::npos ||
86      file_name_str.find('\\') != std::string::npos) {
87    CDM_DLOG() << "Invalid file name.";
88    OnError(OPEN_ERROR);
89    return;
90  }
91
92  // pp::FileRef only accepts path that begins with a '/' character.
93  file_name_ = '/' + file_name_str;
94
95  if (!AcquireFileLock()) {
96    CDM_DLOG() << "File is in use by other cdm::FileIO objects.";
97    OnError(OPEN_WHILE_IN_USE);
98    return;
99  }
100
101  state_ = OPENING_FILE_SYSTEM;
102  OpenFileSystem();
103}
104
105// Call sequence:
106//                                       finished
107// Read() -> ReadFile() -> OnFileRead() ----------> Done.
108//               ^              |
109//               | not finished |
110//               |--------------|
111void CdmFileIOImpl::Read() {
112  CDM_DLOG() << __FUNCTION__;
113  PP_DCHECK(IsMainThread());
114
115  if (state_ == READING_FILE || state_ == WRITING_FILE) {
116    CDM_DLOG() << "Read() called during pending read/write.";
117    OnError(READ_WHILE_IN_USE);
118    return;
119  }
120
121  if (state_ != FILE_OPENED) {
122    CDM_DLOG() << "Read() called in an invalid state.";
123    OnError(READ_ERROR);
124    return;
125  }
126
127  PP_DCHECK(io_buffer_.empty());
128  PP_DCHECK(cumulative_read_buffer_.empty());
129
130  io_buffer_.resize(kReadSize);
131  io_offset_ = 0;
132
133  state_ = READING_FILE;
134  ReadFile();
135}
136
137// Call sequence:
138//                                            finished
139// Write() -> WriteFile() -> OnFileWritten() ----------> Done.
140//                ^                  |
141//                |                  | not finished
142//                |------------------|
143void CdmFileIOImpl::Write(const uint8_t* data, uint32_t data_size) {
144  CDM_DLOG() << __FUNCTION__;
145  PP_DCHECK(IsMainThread());
146
147  if (state_ == READING_FILE || state_ == WRITING_FILE) {
148    CDM_DLOG() << "Write() called during pending read/write.";
149    OnError(WRITE_WHILE_IN_USE);
150    return;
151  }
152
153  if (state_ != FILE_OPENED) {
154    CDM_DLOG() << "Write() called in an invalid state.";
155    OnError(WRITE_ERROR);
156    return;
157  }
158
159  PP_DCHECK(io_offset_ == 0);
160  PP_DCHECK(io_buffer_.empty());
161  if (data_size > 0)
162    io_buffer_.assign(data, data + data_size);
163  else
164    PP_DCHECK(!data);
165
166  state_ = WRITING_FILE;
167
168  // Always SetLength() in case |data_size| is less than the file size.
169  SetLength(data_size);
170}
171
172void CdmFileIOImpl::Close() {
173  CDM_DLOG() << __FUNCTION__;
174  PP_DCHECK(IsMainThread());
175  PP_DCHECK(state_ != FILE_CLOSED);
176  CloseFile();
177  ReleaseFileLock();
178  // All pending callbacks are canceled since |callback_factory_| is destroyed.
179  delete this;
180}
181
182bool CdmFileIOImpl::SetFileID() {
183  PP_DCHECK(file_id_.empty());
184  PP_DCHECK(!file_name_.empty() && file_name_[0] == '/');
185
186  // Not taking ownership of |url_util_dev| (which is a singleton).
187  const pp::URLUtil_Dev* url_util_dev = pp::URLUtil_Dev::Get();
188  PP_URLComponents_Dev components;
189  pp::Var url_var =
190      url_util_dev->GetDocumentURL(pp_instance_handle_, &components);
191  if (!url_var.is_string())
192    return false;
193  std::string url = url_var.AsString();
194
195  file_id_.append(url, components.scheme.begin, components.scheme.len);
196  file_id_ += ':';
197  file_id_.append(url, components.host.begin, components.host.len);
198  file_id_ += ':';
199  file_id_.append(url, components.port.begin, components.port.len);
200  file_id_ += file_name_;
201
202  return true;
203}
204
205bool CdmFileIOImpl::AcquireFileLock() {
206  PP_DCHECK(IsMainThread());
207
208  if (file_id_.empty() && !SetFileID())
209    return false;
210
211  if (!file_lock_map_) {
212    file_lock_map_ = new FileLockMap();
213  } else {
214    FileLockMap::iterator found = file_lock_map_->find(file_id_);
215    if (found != file_lock_map_->end() && found->second)
216      return false;
217  }
218
219  (*file_lock_map_)[file_id_] = true;
220  return true;
221}
222
223void CdmFileIOImpl::ReleaseFileLock() {
224  PP_DCHECK(IsMainThread());
225
226  if (!file_lock_map_)
227    return;
228
229  FileLockMap::iterator found = file_lock_map_->find(file_id_);
230  if (found != file_lock_map_->end() && found->second)
231    found->second = false;
232}
233
234void CdmFileIOImpl::OpenFileSystem() {
235  PP_DCHECK(state_ == OPENING_FILE_SYSTEM);
236
237  pp::CompletionCallbackWithOutput<pp::FileSystem> cb =
238      callback_factory_.NewCallbackWithOutput(
239          &CdmFileIOImpl::OnFileSystemOpened);
240  isolated_file_system_ = pp::IsolatedFileSystemPrivate(
241      pp_instance_handle_, PP_ISOLATEDFILESYSTEMTYPE_PRIVATE_PLUGINPRIVATE);
242
243  CHECK_PP_OK_COMPLETIONPENDING(isolated_file_system_.Open(cb), OPEN_ERROR);
244}
245
246void CdmFileIOImpl::OnFileSystemOpened(int32_t result,
247                                       pp::FileSystem file_system) {
248  PP_DCHECK(IsMainThread());
249  PP_DCHECK(state_ == OPENING_FILE_SYSTEM);
250
251  if (result != PP_OK) {
252    CDM_DLOG() << "File system open failed asynchronously.";
253    ReleaseFileLock();
254    OnError(OPEN_ERROR);
255    return;
256  }
257
258  file_system_ = file_system;
259  state_ = OPENING_FILE;
260  OpenFile();
261}
262
263void CdmFileIOImpl::OpenFile() {
264  PP_DCHECK(state_ == OPENING_FILE);
265
266  file_io_ = pp::FileIO(pp_instance_handle_);
267  file_ref_ = pp::FileRef(file_system_, file_name_.c_str());
268  int32_t file_open_flag = PP_FILEOPENFLAG_READ |
269                           PP_FILEOPENFLAG_WRITE |
270                           PP_FILEOPENFLAG_CREATE;
271  pp::CompletionCallback cb =
272      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileOpened);
273  CHECK_PP_OK_COMPLETIONPENDING(file_io_.Open(file_ref_, file_open_flag, cb),
274                                OPEN_ERROR);
275}
276
277void CdmFileIOImpl::OnFileOpened(int32_t result) {
278  PP_DCHECK(IsMainThread());
279  PP_DCHECK(state_ == OPENING_FILE);
280
281  if (result != PP_OK) {
282    CDM_DLOG() << "File open failed.";
283    ReleaseFileLock();
284    OnError(OPEN_ERROR);
285    return;
286  }
287
288  state_ = FILE_OPENED;
289  client_->OnOpenComplete(cdm::FileIOClient::kSuccess);
290}
291
292void CdmFileIOImpl::ReadFile() {
293  PP_DCHECK(state_ == READING_FILE);
294  PP_DCHECK(!io_buffer_.empty());
295
296  pp::CompletionCallback cb =
297      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileRead);
298  CHECK_PP_OK_COMPLETIONPENDING(
299      file_io_.Read(io_offset_, &io_buffer_[0], io_buffer_.size(), cb),
300      READ_ERROR);
301}
302
303void CdmFileIOImpl::OnFileRead(int32_t bytes_read) {
304  CDM_DLOG() << __FUNCTION__ << ": " << bytes_read;
305  PP_DCHECK(IsMainThread());
306  PP_DCHECK(state_ == READING_FILE);
307
308  // 0 |bytes_read| indicates end-of-file reached.
309  if (bytes_read < PP_OK) {
310    CDM_DLOG() << "Read file failed.";
311    OnError(READ_ERROR);
312    return;
313  }
314
315  PP_DCHECK(static_cast<size_t>(bytes_read) <= io_buffer_.size());
316  // Append |bytes_read| in |io_buffer_| to |cumulative_read_buffer_|.
317  cumulative_read_buffer_.insert(cumulative_read_buffer_.end(),
318                                 io_buffer_.begin(),
319                                 io_buffer_.begin() + bytes_read);
320  io_offset_ += bytes_read;
321
322  // Not received end-of-file yet.
323  if (bytes_read > 0) {
324    ReadFile();
325    return;
326  }
327
328  // We hit end-of-file. Return read data to the client.
329  io_buffer_.clear();
330  io_offset_ = 0;
331  // Clear |cumulative_read_buffer_| in case OnReadComplete() calls Read() or
332  // Write().
333  std::vector<char> local_buffer;
334  std::swap(cumulative_read_buffer_, local_buffer);
335
336  state_ = FILE_OPENED;
337  const uint8_t* data = local_buffer.empty() ?
338      NULL : reinterpret_cast<const uint8_t*>(&local_buffer[0]);
339  client_->OnReadComplete(
340      cdm::FileIOClient::kSuccess, data, local_buffer.size());
341}
342
343void CdmFileIOImpl::SetLength(uint32_t length) {
344  PP_DCHECK(state_ == WRITING_FILE);
345
346  pp::CompletionCallback cb =
347      callback_factory_.NewCallback(&CdmFileIOImpl::OnLengthSet);
348  CHECK_PP_OK_COMPLETIONPENDING(file_io_.SetLength(length, cb), WRITE_ERROR);
349}
350
351void CdmFileIOImpl::OnLengthSet(int32_t result) {
352  CDM_DLOG() << __FUNCTION__ << ": " << result;
353  PP_DCHECK(IsMainThread());
354  PP_DCHECK(state_ == WRITING_FILE);
355
356  if (result != PP_OK) {
357    CDM_DLOG() << "File SetLength failed.";
358    OnError(WRITE_ERROR);
359    return;
360  }
361
362  if (io_buffer_.empty()) {
363    state_ = FILE_OPENED;
364    client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
365    return;
366  }
367
368  WriteFile();
369}
370
371void CdmFileIOImpl::WriteFile() {
372  PP_DCHECK(state_ == WRITING_FILE);
373  PP_DCHECK(io_offset_ < io_buffer_.size());
374
375  pp::CompletionCallback cb =
376      callback_factory_.NewCallback(&CdmFileIOImpl::OnFileWritten);
377  CHECK_PP_OK_COMPLETIONPENDING(file_io_.Write(io_offset_,
378                                               &io_buffer_[io_offset_],
379                                               io_buffer_.size() - io_offset_,
380                                               cb),
381                                WRITE_ERROR);
382}
383
384void CdmFileIOImpl::OnFileWritten(int32_t bytes_written) {
385  CDM_DLOG() << __FUNCTION__ << ": " << bytes_written;
386  PP_DCHECK(IsMainThread());
387  PP_DCHECK(state_ == WRITING_FILE);
388
389  if (bytes_written <= PP_OK) {
390    CDM_DLOG() << "Write file failed.";
391    OnError(READ_ERROR);
392    return;
393  }
394
395  io_offset_ += bytes_written;
396  PP_DCHECK(io_offset_ <= io_buffer_.size());
397
398  if (io_offset_ < io_buffer_.size()) {
399    WriteFile();
400    return;
401  }
402
403  io_buffer_.clear();
404  io_offset_ = 0;
405  state_ = FILE_OPENED;
406  client_->OnWriteComplete(cdm::FileIOClient::kSuccess);
407}
408
409void CdmFileIOImpl::CloseFile() {
410  PP_DCHECK(IsMainThread());
411
412  state_ = FILE_CLOSED;
413
414  file_io_.Close();
415  io_buffer_.clear();
416  io_offset_ = 0;
417  cumulative_read_buffer_.clear();
418}
419
420void CdmFileIOImpl::OnError(ErrorType error_type) {
421  // For *_WHILE_IN_USE errors, do not reset these values. Otherwise, the
422  // existing read/write operation will fail.
423  if (error_type == READ_ERROR || error_type == WRITE_ERROR) {
424    io_buffer_.clear();
425    io_offset_ = 0;
426    cumulative_read_buffer_.clear();
427  }
428  PostOnMain(callback_factory_.NewCallback(&CdmFileIOImpl::NotifyClientOfError,
429                                           error_type));
430}
431
432void CdmFileIOImpl::NotifyClientOfError(int32_t result,
433                                        ErrorType error_type) {
434  PP_DCHECK(result == PP_OK);
435  switch (error_type) {
436    case OPEN_ERROR:
437      client_->OnOpenComplete(cdm::FileIOClient::kError);
438      break;
439    case READ_ERROR:
440      client_->OnReadComplete(cdm::FileIOClient::kError, NULL, 0);
441      break;
442    case WRITE_ERROR:
443      client_->OnWriteComplete(cdm::FileIOClient::kError);
444      break;
445    case OPEN_WHILE_IN_USE:
446      client_->OnOpenComplete(cdm::FileIOClient::kInUse);
447      break;
448    case READ_WHILE_IN_USE:
449      client_->OnReadComplete(cdm::FileIOClient::kInUse, NULL, 0);
450      break;
451    case WRITE_WHILE_IN_USE:
452      client_->OnWriteComplete(cdm::FileIOClient::kInUse);
453      break;
454  }
455}
456
457}  // namespace media
458