Home | History | Annotate | Download | only in midi
      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 "media/midi/usb_midi_descriptor_parser.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/logging.h"
     10 
     11 namespace media {
     12 
     13 namespace {
     14 
     15 // The constants below are specified in USB spec, USB audio spec
     16 // and USB midi spec.
     17 
     18 enum DescriptorType {
     19   TYPE_DEVICE = 1,
     20   TYPE_CONFIGURATION = 2,
     21   TYPE_STRING = 3,
     22   TYPE_INTERFACE = 4,
     23   TYPE_ENDPOINT = 5,
     24   TYPE_DEVICE_QUALIFIER = 6,
     25   TYPE_OTHER_SPEED_CONFIGURATION = 7,
     26   TYPE_INTERFACE_POWER = 8,
     27 
     28   TYPE_CS_INTERFACE = 36,
     29   TYPE_CS_ENDPOINT = 37,
     30 };
     31 
     32 enum DescriptorSubType {
     33   SUBTYPE_MS_DESCRIPTOR_UNDEFINED = 0,
     34   SUBTYPE_MS_HEADER = 1,
     35   SUBTYPE_MIDI_IN_JACK = 2,
     36   SUBTYPE_MIDI_OUT_JACK = 3,
     37   SUBTYPE_ELEMENT = 4,
     38 };
     39 
     40 enum JackType {
     41   JACK_TYPE_UNDEFINED = 0,
     42   JACK_TYPE_EMBEDDED = 1,
     43   JACK_TYPE_EXTERNAL = 2,
     44 };
     45 
     46 const uint8 kAudioInterfaceClass = 1;
     47 const uint8 kAudioMidiInterfaceSubclass = 3;
     48 
     49 class JackMatcher {
     50  public:
     51   explicit JackMatcher(uint8 id) : id_(id) {}
     52 
     53   bool operator() (const UsbMidiJack& jack) const {
     54     return jack.jack_id == id_;
     55   }
     56 
     57  private:
     58   uint8 id_;
     59 };
     60 
     61 }  // namespace
     62 
     63 UsbMidiDescriptorParser::UsbMidiDescriptorParser()
     64     : is_parsing_usb_midi_interface_(false),
     65       current_endpoint_address_(0),
     66       current_cable_number_(0) {}
     67 
     68 UsbMidiDescriptorParser::~UsbMidiDescriptorParser() {}
     69 
     70 bool UsbMidiDescriptorParser::Parse(UsbMidiDevice* device,
     71                                     const uint8* data,
     72                                     size_t size,
     73                                     std::vector<UsbMidiJack>* jacks) {
     74   jacks->clear();
     75   bool result = ParseInternal(device, data, size, jacks);
     76   if (!result)
     77     jacks->clear();
     78   Clear();
     79   return result;
     80 }
     81 
     82 bool UsbMidiDescriptorParser::ParseInternal(UsbMidiDevice* device,
     83                                             const uint8* data,
     84                                             size_t size,
     85                                             std::vector<UsbMidiJack>* jacks) {
     86   for (const uint8* current = data;
     87        current < data + size;
     88        current += current[0]) {
     89     uint8 length = current[0];
     90     if (length < 2) {
     91       DVLOG(1) << "Descriptor Type is not accessible.";
     92       return false;
     93     }
     94     if (current + length > data + size) {
     95       DVLOG(1) << "The header size is incorrect.";
     96       return false;
     97     }
     98     DescriptorType descriptor_type = static_cast<DescriptorType>(current[1]);
     99     if (descriptor_type != TYPE_INTERFACE && !is_parsing_usb_midi_interface_)
    100       continue;
    101 
    102     switch (descriptor_type) {
    103       case TYPE_INTERFACE:
    104         if (!ParseInterface(current, length))
    105           return false;
    106         break;
    107       case TYPE_CS_INTERFACE:
    108         // We are assuming that the corresponding INTERFACE precedes
    109         // the CS_INTERFACE descriptor, as specified.
    110         if (!ParseCSInterface(device, current, length))
    111           return false;
    112         break;
    113       case TYPE_ENDPOINT:
    114         // We are assuming that endpoints are contained in an interface.
    115         if (!ParseEndpoint(current, length))
    116           return false;
    117         break;
    118       case TYPE_CS_ENDPOINT:
    119         // We are assuming that the corresponding ENDPOINT precedes
    120         // the CS_ENDPOINT descriptor, as specified.
    121         if (!ParseCSEndpoint(current, length, jacks))
    122           return false;
    123         break;
    124       default:
    125         // Ignore uninteresting types.
    126         break;
    127     }
    128   }
    129   return true;
    130 }
    131 
    132 bool UsbMidiDescriptorParser::ParseInterface(const uint8* data, size_t size) {
    133   if (size != 9) {
    134     DVLOG(1) << "INTERFACE header size is incorrect.";
    135     return false;
    136   }
    137   incomplete_jacks_.clear();
    138 
    139   uint8 interface_class = data[5];
    140   uint8 interface_subclass = data[6];
    141 
    142   // All descriptors of endpoints contained in this interface
    143   // precede the next INTERFACE descriptor.
    144   is_parsing_usb_midi_interface_ =
    145       interface_class == kAudioInterfaceClass &&
    146       interface_subclass == kAudioMidiInterfaceSubclass;
    147   return true;
    148 }
    149 
    150 bool UsbMidiDescriptorParser::ParseCSInterface(UsbMidiDevice* device,
    151                                                const uint8* data,
    152                                                size_t size) {
    153   // Descriptor Type and Descriptor Subtype should be accessible.
    154   if (size < 3) {
    155     DVLOG(1) << "CS_INTERFACE header size is incorrect.";
    156     return false;
    157   }
    158 
    159   DescriptorSubType subtype = static_cast<DescriptorSubType>(data[2]);
    160 
    161   if (subtype != SUBTYPE_MIDI_OUT_JACK &&
    162       subtype != SUBTYPE_MIDI_IN_JACK)
    163     return true;
    164 
    165   if (size < 6) {
    166     DVLOG(1) << "CS_INTERFACE (MIDI JACK) header size is incorrect.";
    167     return false;
    168   }
    169   uint8 jack_type = data[3];
    170   uint8 id = data[4];
    171   if (jack_type == JACK_TYPE_EMBEDDED) {
    172     // We can't determine the associated endpoint now.
    173     incomplete_jacks_.push_back(UsbMidiJack(device, id, 0, 0));
    174   }
    175   return true;
    176 }
    177 
    178 bool UsbMidiDescriptorParser::ParseEndpoint(const uint8* data, size_t size) {
    179   if (size < 4) {
    180     DVLOG(1) << "ENDPOINT header size is incorrect.";
    181     return false;
    182   }
    183   current_endpoint_address_ = data[2];
    184   current_cable_number_ = 0;
    185   return true;
    186 }
    187 
    188 bool UsbMidiDescriptorParser::ParseCSEndpoint(const uint8* data,
    189                                               size_t size,
    190                                               std::vector<UsbMidiJack>* jacks) {
    191   const size_t kSizeForEmptyJacks = 4;
    192   // CS_ENDPOINT must be of size 4 + n where n is the number of associated
    193   // jacks.
    194   if (size < kSizeForEmptyJacks) {
    195     DVLOG(1) << "CS_ENDPOINT header size is incorrect.";
    196     return false;
    197   }
    198   uint8 num_jacks = data[3];
    199   if (size != kSizeForEmptyJacks + num_jacks) {
    200     DVLOG(1) << "CS_ENDPOINT header size is incorrect.";
    201     return false;
    202   }
    203 
    204   for (size_t i = 0; i < num_jacks; ++i) {
    205     uint8 jack = data[kSizeForEmptyJacks + i];
    206     std::vector<UsbMidiJack>::iterator it =
    207         std::find_if(incomplete_jacks_.begin(),
    208                      incomplete_jacks_.end(),
    209                      JackMatcher(jack));
    210     if (it == incomplete_jacks_.end()) {
    211       DVLOG(1) << "A non-existing MIDI jack is associated.";
    212       return false;
    213     }
    214     if (current_cable_number_ > 0xf) {
    215       DVLOG(1) << "Cable number should range from 0x0 to 0xf.";
    216       return false;
    217     }
    218     // CS_ENDPOINT follows ENDPOINT and hence we can use the following
    219     // member variables.
    220     it->cable_number = current_cable_number_++;
    221     it->endpoint_address = current_endpoint_address_;
    222     jacks->push_back(*it);
    223     incomplete_jacks_.erase(it);
    224   }
    225   return true;
    226 }
    227 
    228 void UsbMidiDescriptorParser::Clear() {
    229   is_parsing_usb_midi_interface_ = false;
    230   current_endpoint_address_ = 0;
    231   current_cable_number_ = 0;
    232   incomplete_jacks_.clear();
    233 }
    234 
    235 }  // namespace media
    236