/* * Copyright (C) 2013 Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "modules/webmidi/MIDIOutput.h" #include "bindings/core/v8/ExceptionState.h" #include "core/dom/ExceptionCode.h" #include "core/dom/ExecutionContext.h" #include "core/frame/LocalDOMWindow.h" #include "core/timing/DOMWindowPerformance.h" #include "core/timing/Performance.h" #include "modules/webmidi/MIDIAccess.h" namespace blink { using PortState = MIDIAccessor::MIDIPortState; namespace { double now(ExecutionContext* context) { LocalDOMWindow* window = context ? context->executingWindow() : nullptr; Performance* performance = window ? DOMWindowPerformance::performance(*window) : nullptr; return performance ? performance->now() : 0.0; } class MessageValidator { public: static bool validate(DOMUint8Array* array, ExceptionState& exceptionState, bool sysexEnabled) { MessageValidator validator(array); return validator.process(exceptionState, sysexEnabled); } private: MessageValidator(DOMUint8Array* array) : m_data(array->data()) , m_length(array->length()) , m_offset(0) { } bool process(ExceptionState& exceptionState, bool sysexEnabled) { while (!isEndOfData() && acceptRealTimeMessages()) { if (!isStatusByte()) { exceptionState.throwTypeError("Running status is not allowed " + getPositionString()); return false; } if (isEndOfSysex()) { exceptionState.throwTypeError("Unexpected end of system exclusive message " + getPositionString()); return false; } if (isReservedStatusByte()) { exceptionState.throwTypeError("Reserved status is not allowed " + getPositionString()); return false; } if (isSysex()) { if (!sysexEnabled) { exceptionState.throwDOMException(InvalidAccessError, "System exclusive message is not allowed " + getPositionString()); return false; } if (!acceptCurrentSysex()) { if (isEndOfData()) exceptionState.throwTypeError("System exclusive message is not ended by end of system exclusive message."); else exceptionState.throwTypeError("System exclusive message contains a status byte " + getPositionString()); return false; } } else { if (!acceptCurrentMessage()) { if (isEndOfData()) exceptionState.throwTypeError("Message is incomplete."); else exceptionState.throwTypeError("Unexpected status byte at index " + getPositionString()); return false; } } } return true; } private: bool isEndOfData() { return m_offset >= m_length; } bool isSysex() { return m_data[m_offset] == 0xf0; } bool isSystemMessage() { return m_data[m_offset] >= 0xf0; } bool isEndOfSysex() { return m_data[m_offset] == 0xf7; } bool isRealTimeMessage() { return m_data[m_offset] >= 0xf8; } bool isStatusByte() { return m_data[m_offset] & 0x80; } bool isReservedStatusByte() { return m_data[m_offset] == 0xf4 || m_data[m_offset] == 0xf5 || m_data[m_offset] == 0xf9 || m_data[m_offset] == 0xfd; } bool acceptRealTimeMessages() { for (; !isEndOfData(); m_offset++) { if (isRealTimeMessage() && !isReservedStatusByte()) continue; return true; } return false; } bool acceptCurrentSysex() { ASSERT(isSysex()); for (m_offset++; !isEndOfData(); m_offset++) { if (isReservedStatusByte()) return false; if (isRealTimeMessage()) continue; if (isEndOfSysex()) { m_offset++; return true; } if (isStatusByte()) return false; } return false; } bool acceptCurrentMessage() { ASSERT(isStatusByte()); ASSERT(!isSysex()); ASSERT(!isReservedStatusByte()); ASSERT(!isRealTimeMessage()); static const int channelMessageLength[7] = { 3, 3, 3, 3, 2, 2, 3 }; // for 0x8*, 0x9*, ..., 0xe* static const int systemMessageLength[7] = { 2, 3, 2, 0, 0, 1, 0 }; // for 0xf1, 0xf2, ..., 0xf7 size_t length = isSystemMessage() ? systemMessageLength[m_data[m_offset] - 0xf1] : channelMessageLength[(m_data[m_offset] >> 4) - 8]; size_t count = 1; for (m_offset++; !isEndOfData(); m_offset++) { if (isReservedStatusByte()) return false; if (isRealTimeMessage()) continue; if (isStatusByte()) return false; if (++count == length) { m_offset++; return true; } } return false; } String getPositionString() { return "at index " + String::number(m_offset) + " (" + String::number(m_data[m_offset]) + ")."; } const unsigned char* m_data; const size_t m_length; size_t m_offset; }; } // namespace MIDIOutput* MIDIOutput::create(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version, PortState state) { ASSERT(access); MIDIOutput* output = new MIDIOutput(access, portIndex, id, manufacturer, name, version, state); output->suspendIfNeeded(); return output; } MIDIOutput::MIDIOutput(MIDIAccess* access, unsigned portIndex, const String& id, const String& manufacturer, const String& name, const String& version, PortState state) : MIDIPort(access, id, manufacturer, name, TypeOutput, version, state) , m_portIndex(portIndex) { } MIDIOutput::~MIDIOutput() { } void MIDIOutput::send(DOMUint8Array* array, double timestamp, ExceptionState& exceptionState) { ASSERT(array); if (timestamp == 0.0) timestamp = now(getExecutionContext()); // Implicit open. It does nothing if the port is already opened. // This should be performed even if |array| is invalid. open(); if (MessageValidator::validate(array, exceptionState, midiAccess()->sysexEnabled())) midiAccess()->sendMIDIData(m_portIndex, array->data(), array->length(), timestamp); } void MIDIOutput::send(Vector unsignedData, double timestamp, ExceptionState& exceptionState) { if (timestamp == 0.0) timestamp = now(getExecutionContext()); RefPtr array = DOMUint8Array::create(unsignedData.size()); DOMUint8Array::ValueType* const arrayData = array->data(); const uint32_t arrayLength = array->length(); for (size_t i = 0; i < unsignedData.size(); ++i) { if (unsignedData[i] > 0xff) { exceptionState.throwTypeError("The value at index " + String::number(i) + " (" + String::number(unsignedData[i]) + ") is greater than 0xFF."); return; } if (i < arrayLength) arrayData[i] = unsignedData[i] & 0xff; } send(array.get(), timestamp, exceptionState); } void MIDIOutput::send(DOMUint8Array* data, ExceptionState& exceptionState) { ASSERT(data); send(data, 0.0, exceptionState); } void MIDIOutput::send(Vector unsignedData, ExceptionState& exceptionState) { send(unsignedData, 0.0, exceptionState); } DEFINE_TRACE(MIDIOutput) { MIDIPort::trace(visitor); } } // namespace blink