Home | History | Annotate | Download | only in media
      1 // Copyright (c) 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 "content/browser/renderer_host/media/midi_host.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/debug/trace_event.h"
     10 #include "base/process/process.h"
     11 #include "content/browser/browser_main_loop.h"
     12 #include "content/browser/child_process_security_policy_impl.h"
     13 #include "content/browser/media/media_internals.h"
     14 #include "content/common/media/midi_messages.h"
     15 #include "content/public/browser/content_browser_client.h"
     16 #include "content/public/browser/media_observer.h"
     17 #include "content/public/browser/user_metrics.h"
     18 #include "media/midi/midi_manager.h"
     19 #include "media/midi/midi_message_queue.h"
     20 #include "media/midi/midi_message_util.h"
     21 
     22 using media::MIDIManager;
     23 using media::MIDIPortInfoList;
     24 
     25 namespace content {
     26 namespace {
     27 
     28 // The total number of bytes which we're allowed to send to the OS
     29 // before knowing that they have been successfully sent.
     30 const size_t kMaxInFlightBytes = 10 * 1024 * 1024;  // 10 MB.
     31 
     32 // We keep track of the number of bytes successfully sent to
     33 // the hardware.  Every once in a while we report back to the renderer
     34 // the number of bytes sent since the last report. This threshold determines
     35 // how many bytes will be sent before reporting back to the renderer.
     36 const size_t kAcknowledgementThresholdBytes = 1024 * 1024;  // 1 MB.
     37 
     38 const uint8 kSysExMessage = 0xf0;
     39 const uint8 kEndOfSysExMessage = 0xf7;
     40 
     41 bool IsDataByte(uint8 data) {
     42   return (data & 0x80) == 0;
     43 }
     44 
     45 bool IsSystemRealTimeMessage(uint8 data) {
     46   return 0xf8 <= data && data <= 0xff;
     47 }
     48 
     49 }  // namespace
     50 
     51 MIDIHost::MIDIHost(int renderer_process_id, media::MIDIManager* midi_manager)
     52     : renderer_process_id_(renderer_process_id),
     53       has_sys_ex_permission_(false),
     54       midi_manager_(midi_manager),
     55       sent_bytes_in_flight_(0),
     56       bytes_sent_since_last_acknowledgement_(0) {
     57 }
     58 
     59 MIDIHost::~MIDIHost() {
     60   if (midi_manager_)
     61     midi_manager_->EndSession(this);
     62 }
     63 
     64 void MIDIHost::OnDestruct() const {
     65   BrowserThread::DeleteOnIOThread::Destruct(this);
     66 }
     67 
     68 ///////////////////////////////////////////////////////////////////////////////
     69 // IPC Messages handler
     70 bool MIDIHost::OnMessageReceived(const IPC::Message& message,
     71                                  bool* message_was_ok) {
     72   bool handled = true;
     73   IPC_BEGIN_MESSAGE_MAP_EX(MIDIHost, message, *message_was_ok)
     74     IPC_MESSAGE_HANDLER(MIDIHostMsg_StartSession, OnStartSession)
     75     IPC_MESSAGE_HANDLER(MIDIHostMsg_SendData, OnSendData)
     76     IPC_MESSAGE_UNHANDLED(handled = false)
     77   IPC_END_MESSAGE_MAP_EX()
     78 
     79   return handled;
     80 }
     81 
     82 void MIDIHost::OnStartSession(int client_id) {
     83   MIDIPortInfoList input_ports;
     84   MIDIPortInfoList output_ports;
     85 
     86   // Initialize devices and register to receive MIDI data.
     87   bool success = false;
     88   if (midi_manager_) {
     89     success = midi_manager_->StartSession(this);
     90     if (success) {
     91       input_ports = midi_manager_->input_ports();
     92       output_ports = midi_manager_->output_ports();
     93       received_messages_queues_.clear();
     94       received_messages_queues_.resize(input_ports.size());
     95       // ChildSecurityPolicy is set just before OnStartSession by
     96       // MIDIDispatcherHost. So we can safely cache the policy.
     97       has_sys_ex_permission_ = ChildProcessSecurityPolicyImpl::GetInstance()->
     98           CanSendMIDISysExMessage(renderer_process_id_);
     99     }
    100   }
    101 
    102   Send(new MIDIMsg_SessionStarted(
    103        client_id,
    104        success,
    105        input_ports,
    106        output_ports));
    107 }
    108 
    109 void MIDIHost::OnSendData(uint32 port,
    110                           const std::vector<uint8>& data,
    111                           double timestamp) {
    112   if (!midi_manager_)
    113     return;
    114 
    115   if (data.empty())
    116     return;
    117 
    118   // Blink running in a renderer checks permission to raise a SecurityError
    119   // in JavaScript. The actual permission check for security purposes
    120   // happens here in the browser process.
    121   if (!has_sys_ex_permission_ &&
    122       (std::find(data.begin(), data.end(), kSysExMessage) != data.end())) {
    123     RecordAction(UserMetricsAction("BadMessageTerminate_MIDI"));
    124     BadMessageReceived();
    125     return;
    126   }
    127 
    128   if (!IsValidWebMIDIData(data))
    129     return;
    130 
    131   base::AutoLock auto_lock(in_flight_lock_);
    132   // Sanity check that we won't send too much data.
    133   // TODO(yukawa): Consider to send an error event back to the renderer
    134   // after some future discussion in W3C.
    135   if (data.size() + sent_bytes_in_flight_ > kMaxInFlightBytes)
    136     return;
    137   midi_manager_->DispatchSendMIDIData(this, port, data, timestamp);
    138   sent_bytes_in_flight_ += data.size();
    139 }
    140 
    141 void MIDIHost::ReceiveMIDIData(
    142     uint32 port,
    143     const uint8* data,
    144     size_t length,
    145     double timestamp) {
    146   TRACE_EVENT0("midi", "MIDIHost::ReceiveMIDIData");
    147 
    148   if (received_messages_queues_.size() <= port)
    149     return;
    150 
    151   // Lazy initialization
    152   if (received_messages_queues_[port] == NULL)
    153     received_messages_queues_[port] = new media::MIDIMessageQueue(true);
    154 
    155   received_messages_queues_[port]->Add(data, length);
    156   std::vector<uint8> message;
    157   while (true) {
    158     received_messages_queues_[port]->Get(&message);
    159     if (message.empty())
    160       break;
    161 
    162     // MIDI devices may send a system exclusive messages even if the renderer
    163     // doesn't have a permission to receive it. Don't kill the renderer as
    164     // OnSendData() does.
    165     if (message[0] == kSysExMessage && !has_sys_ex_permission_)
    166       continue;
    167 
    168     // Send to the renderer.
    169     Send(new MIDIMsg_DataReceived(port, message, timestamp));
    170   }
    171 }
    172 
    173 void MIDIHost::AccumulateMIDIBytesSent(size_t n) {
    174   {
    175     base::AutoLock auto_lock(in_flight_lock_);
    176     if (n <= sent_bytes_in_flight_)
    177       sent_bytes_in_flight_ -= n;
    178   }
    179 
    180   if (bytes_sent_since_last_acknowledgement_ + n >=
    181       bytes_sent_since_last_acknowledgement_)
    182     bytes_sent_since_last_acknowledgement_ += n;
    183 
    184   if (bytes_sent_since_last_acknowledgement_ >=
    185       kAcknowledgementThresholdBytes) {
    186     Send(new MIDIMsg_AcknowledgeSentData(
    187         bytes_sent_since_last_acknowledgement_));
    188     bytes_sent_since_last_acknowledgement_ = 0;
    189   }
    190 }
    191 
    192 // static
    193 bool MIDIHost::IsValidWebMIDIData(const std::vector<uint8>& data) {
    194   bool in_sysex = false;
    195   size_t waiting_data_length = 0;
    196   for (size_t i = 0; i < data.size(); ++i) {
    197     const uint8 current = data[i];
    198     if (IsSystemRealTimeMessage(current))
    199       continue;  // Real time message can be placed at any point.
    200     if (waiting_data_length > 0) {
    201       if (!IsDataByte(current))
    202         return false;  // Error: |current| should have been data byte.
    203       --waiting_data_length;
    204       continue;  // Found data byte as expected.
    205     }
    206     if (in_sysex) {
    207       if (data[i] == kEndOfSysExMessage)
    208         in_sysex = false;
    209       else if (!IsDataByte(current))
    210         return false;  // Error: |current| should have been data byte.
    211       continue;  // Found data byte as expected.
    212     }
    213     if (current == kSysExMessage) {
    214       in_sysex = true;
    215       continue;  // Found SysEX
    216     }
    217     waiting_data_length = media::GetMIDIMessageLength(current);
    218     if (waiting_data_length == 0)
    219       return false;  // Error: |current| should have been a valid status byte.
    220     --waiting_data_length;  // Found status byte
    221   }
    222   return waiting_data_length == 0 && !in_sysex;
    223 }
    224 
    225 }  // namespace content
    226