1 /* 2 * Copyright (C) 2013 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31 #include "config.h" 32 #include "modules/webmidi/MIDIOutput.h" 33 34 #include "bindings/core/v8/ExceptionState.h" 35 #include "core/dom/ExceptionCode.h" 36 #include "core/dom/ExecutionContext.h" 37 #include "core/frame/LocalDOMWindow.h" 38 #include "core/timing/Performance.h" 39 #include "modules/webmidi/MIDIAccess.h" 40 41 namespace blink { 42 43 namespace { 44 45 double now(ExecutionContext* context) 46 { 47 LocalDOMWindow* window = context ? context->executingWindow() : 0; 48 Performance* performance = window ? &window->performance() : 0; 49 return performance ? performance->now() : 0.0; 50 } 51 52 class MessageValidator { 53 public: 54 static bool validate(Uint8Array* array, ExceptionState& exceptionState, bool sysexEnabled) 55 { 56 MessageValidator validator(array); 57 return validator.process(exceptionState, sysexEnabled); 58 } 59 private: 60 MessageValidator(Uint8Array* array) 61 : m_data(array->data()) 62 , m_length(array->length()) 63 , m_offset(0) { } 64 65 bool process(ExceptionState& exceptionState, bool sysexEnabled) 66 { 67 while (!isEndOfData() && acceptRealTimeMessages()) { 68 if (!isStatusByte()) { 69 exceptionState.throwTypeError("Running status is not allowed " + getPositionString()); 70 return false; 71 } 72 if (isEndOfSysex()) { 73 exceptionState.throwTypeError("Unexpected end of system exclusive message " + getPositionString()); 74 return false; 75 } 76 if (isReservedStatusByte()) { 77 exceptionState.throwTypeError("Reserved status is not allowed " + getPositionString()); 78 return false; 79 } 80 if (isSysex()) { 81 if (!sysexEnabled) { 82 exceptionState.throwDOMException(InvalidAccessError, "System exclusive message is not allowed " + getPositionString()); 83 return false; 84 } 85 if (!acceptCurrentSysex()) { 86 if (isEndOfData()) 87 exceptionState.throwTypeError("System exclusive message is not ended by end of system exclusive message."); 88 else 89 exceptionState.throwTypeError("System exclusive message contains a status byte " + getPositionString()); 90 return false; 91 } 92 } else { 93 if (!acceptCurrentMessage()) { 94 if (isEndOfData()) 95 exceptionState.throwTypeError("Message is incomplete."); 96 else 97 exceptionState.throwTypeError("Unexpected status byte at index " + getPositionString()); 98 return false; 99 } 100 } 101 } 102 return true; 103 } 104 105 private: 106 bool isEndOfData() { return m_offset >= m_length; } 107 bool isSysex() { return m_data[m_offset] == 0xf0; } 108 bool isSystemMessage() { return m_data[m_offset] >= 0xf0; } 109 bool isEndOfSysex() { return m_data[m_offset] == 0xf7; } 110 bool isRealTimeMessage() { return m_data[m_offset] >= 0xf8; } 111 bool isStatusByte() { return m_data[m_offset] & 0x80; } 112 bool isReservedStatusByte() { return m_data[m_offset] == 0xf4 || m_data[m_offset] == 0xf5 || m_data[m_offset] == 0xf9 || m_data[m_offset] == 0xfd; } 113 114 bool acceptRealTimeMessages() 115 { 116 for (; !isEndOfData(); m_offset++) { 117 if (isRealTimeMessage() && !isReservedStatusByte()) 118 continue; 119 return true; 120 } 121 return false; 122 } 123 124 bool acceptCurrentSysex() 125 { 126 ASSERT(isSysex()); 127 for (m_offset++; !isEndOfData(); m_offset++) { 128 if (isReservedStatusByte()) 129 return false; 130 if (isRealTimeMessage()) 131 continue; 132 if (isEndOfSysex()) { 133 m_offset++; 134 return true; 135 } 136 if (isStatusByte()) 137 return false; 138 } 139 return false; 140 } 141 142 bool acceptCurrentMessage() 143 { 144 ASSERT(isStatusByte()); 145 ASSERT(!isSysex()); 146 ASSERT(!isReservedStatusByte()); 147 ASSERT(!isRealTimeMessage()); 148 static const int channelMessageLength[7] = { 3, 3, 3, 3, 2, 2, 3 }; // for 0x8*, 0x9*, ..., 0xe* 149 static const int systemMessageLength[7] = { 2, 3, 2, 0, 0, 1, 0 }; // for 0xf1, 0xf2, ..., 0xf7 150 size_t length = isSystemMessage() ? systemMessageLength[m_data[m_offset] - 0xf1] : channelMessageLength[(m_data[m_offset] >> 4) - 8]; 151 size_t count = 1; 152 for (m_offset++; !isEndOfData(); m_offset++) { 153 if (isReservedStatusByte()) 154 return false; 155 if (isRealTimeMessage()) 156 continue; 157 if (isStatusByte()) 158 return false; 159 if (++count == length) { 160 m_offset++; 161 return true; 162 } 163 } 164 return false; 165 } 166 167 String getPositionString() { return "at index " + String::number(m_offset) + " (" + String::number(m_data[m_offset]) + ")."; } 168 169 const unsigned char* m_data; 170 const size_t m_length; 171 size_t m_offset; 172 }; 173 174 } // namespace 175 176 MIDIOutput* MIDIOutput::create(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version) 177 { 178 ASSERT(access); 179 return adoptRefCountedGarbageCollectedWillBeNoop(new MIDIOutput(access, portIndex, id, manufacturer, name, version)); 180 } 181 182 MIDIOutput::MIDIOutput(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version) 183 : MIDIPort(access, id, manufacturer, name, MIDIPortTypeOutput, version) 184 , m_portIndex(portIndex) 185 { 186 } 187 188 MIDIOutput::~MIDIOutput() 189 { 190 } 191 192 void MIDIOutput::send(Uint8Array* array, double timestamp, ExceptionState& exceptionState) 193 { 194 if (timestamp == 0.0) 195 timestamp = now(executionContext()); 196 197 if (!array) 198 return; 199 200 if (MessageValidator::validate(array, exceptionState, midiAccess()->sysexEnabled())) 201 midiAccess()->sendMIDIData(m_portIndex, array->data(), array->length(), timestamp); 202 } 203 204 void MIDIOutput::send(Vector<unsigned> unsignedData, double timestamp, ExceptionState& exceptionState) 205 { 206 if (timestamp == 0.0) 207 timestamp = now(executionContext()); 208 209 RefPtr<Uint8Array> array = Uint8Array::create(unsignedData.size()); 210 211 for (size_t i = 0; i < unsignedData.size(); ++i) { 212 if (unsignedData[i] > 0xff) { 213 exceptionState.throwTypeError("The value at index " + String::number(i) + " (" + String::number(unsignedData[i]) + ") is greater than 0xFF."); 214 return; 215 } 216 unsigned char value = unsignedData[i] & 0xff; 217 array->set(i, value); 218 } 219 220 send(array.get(), timestamp, exceptionState); 221 } 222 223 void MIDIOutput::send(Uint8Array* data, ExceptionState& exceptionState) 224 { 225 send(data, 0.0, exceptionState); 226 } 227 228 void MIDIOutput::send(Vector<unsigned> unsignedData, ExceptionState& exceptionState) 229 { 230 send(unsignedData, 0.0, exceptionState); 231 } 232 233 void MIDIOutput::trace(Visitor* visitor) 234 { 235 MIDIPort::trace(visitor); 236 } 237 238 } // namespace blink 239