blob: 655414e858e1c9b38f9f0a5bf475d533fbf70e0a [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/midi_host.h"
#include <stddef.h>
#include <stdint.h>
#include "base/macros.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/strings/stringprintf.h"
#include "content/common/media/midi_messages.h"
#include "content/public/test/test_browser_thread.h"
#include "media/midi/midi_manager.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace content {
namespace {
const uint8_t kGMOn[] = {0xf0, 0x7e, 0x7f, 0x09, 0x01, 0xf7};
const uint8_t kGSOn[] = {
0xf0, 0x41, 0x10, 0x42, 0x12, 0x40, 0x00, 0x7f, 0x00, 0x41, 0xf7,
};
const uint8_t kNoteOn[] = {0x90, 0x3c, 0x7f};
const uint8_t kNoteOnWithRunningStatus[] = {
0x90, 0x3c, 0x7f, 0x3c, 0x7f, 0x3c, 0x7f,
};
const uint8_t kChannelPressure[] = {0xd0, 0x01};
const uint8_t kChannelPressureWithRunningStatus[] = {
0xd0, 0x01, 0x01, 0x01,
};
const uint8_t kTimingClock[] = {0xf8};
const uint8_t kBrokenData1[] = {0x90};
const uint8_t kBrokenData2[] = {0xf7};
const uint8_t kBrokenData3[] = {0xf2, 0x00};
const uint8_t kDataByte0[] = {0x00};
const int kRenderProcessId = 0;
template <typename T, size_t N>
const std::vector<T> AsVector(const T(&data)[N]) {
std::vector<T> buffer;
buffer.insert(buffer.end(), data, data + N);
return buffer;
}
template <typename T, size_t N>
void PushToVector(const T(&data)[N], std::vector<T>* buffer) {
buffer->insert(buffer->end(), data, data + N);
}
enum MidiEventType {
DISPATCH_SEND_MIDI_DATA,
};
struct MidiEvent {
MidiEvent(MidiEventType in_type,
uint32_t in_port_index,
const std::vector<uint8_t>& in_data,
double in_timestamp)
: type(in_type),
port_index(in_port_index),
data(in_data),
timestamp(in_timestamp) {}
MidiEventType type;
uint32_t port_index;
std::vector<uint8_t> data;
double timestamp;
};
class FakeMidiManager : public media::midi::MidiManager {
public:
void DispatchSendMidiData(media::midi::MidiManagerClient* client,
uint32_t port_index,
const std::vector<uint8_t>& data,
double timestamp) override {
events_.push_back(MidiEvent(DISPATCH_SEND_MIDI_DATA,
port_index,
data,
timestamp));
}
std::vector<MidiEvent> events_;
};
class MidiHostForTesting : public MidiHost {
public:
MidiHostForTesting(int renderer_process_id,
media::midi::MidiManager* midi_manager)
: MidiHost(renderer_process_id, midi_manager) {}
private:
~MidiHostForTesting() override {}
// BrowserMessageFilter implementation.
// Override ShutdownForBadMessage() to do nothing since the original
// implementation to kill a malicious renderer process causes a check failure
// in unit tests.
void ShutdownForBadMessage() override {}
};
class MidiHostTest : public testing::Test {
public:
MidiHostTest()
: io_browser_thread_(BrowserThread::IO, &message_loop_),
host_(new MidiHostForTesting(kRenderProcessId, &manager_)),
data_(kNoteOn, kNoteOn + arraysize(kNoteOn)),
port_id_(0) {}
~MidiHostTest() override {
manager_.Shutdown();
RunLoopUntilIdle();
}
protected:
void AddOutputPort() {
const std::string id = base::StringPrintf("i-can-%d", port_id_++);
const std::string manufacturer("yukatan");
const std::string name("doki-doki-pi-pine");
const std::string version("3.14159265359");
media::midi::MidiPortState state = media::midi::MIDI_PORT_CONNECTED;
media::midi::MidiPortInfo info(id, manufacturer, name, version, state);
host_->AddOutputPort(info);
}
void OnSendData(uint32_t port) {
scoped_ptr<IPC::Message> message(
new MidiHostMsg_SendData(port, data_, 0.0));
host_->OnMessageReceived(*message.get());
}
size_t GetEventSize() const {
return manager_.events_.size();
}
void CheckSendEventAt(size_t at, uint32_t port) {
EXPECT_EQ(DISPATCH_SEND_MIDI_DATA, manager_.events_[at].type);
EXPECT_EQ(port, manager_.events_[at].port_index);
EXPECT_EQ(data_, manager_.events_[at].data);
EXPECT_EQ(0.0, manager_.events_[at].timestamp);
}
void RunLoopUntilIdle() {
base::RunLoop run_loop;
run_loop.RunUntilIdle();
}
private:
base::MessageLoop message_loop_;
TestBrowserThread io_browser_thread_;
FakeMidiManager manager_;
scoped_refptr<MidiHostForTesting> host_;
std::vector<uint8_t> data_;
int32_t port_id_;
DISALLOW_COPY_AND_ASSIGN(MidiHostTest);
};
} // namespace
TEST_F(MidiHostTest, IsValidWebMIDIData) {
// Test single event scenario
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(AsVector(kGMOn)));
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(AsVector(kGSOn)));
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(AsVector(kNoteOn)));
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(AsVector(kChannelPressure)));
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(AsVector(kTimingClock)));
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(AsVector(kBrokenData1)));
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(AsVector(kBrokenData2)));
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(AsVector(kBrokenData3)));
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(AsVector(kDataByte0)));
// MIDI running status should be disallowed
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(
AsVector(kNoteOnWithRunningStatus)));
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(
AsVector(kChannelPressureWithRunningStatus)));
// Multiple messages are allowed as long as each of them is complete.
{
std::vector<uint8_t> buffer;
PushToVector(kGMOn, &buffer);
PushToVector(kNoteOn, &buffer);
PushToVector(kGSOn, &buffer);
PushToVector(kTimingClock, &buffer);
PushToVector(kNoteOn, &buffer);
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(buffer));
PushToVector(kBrokenData1, &buffer);
EXPECT_FALSE(MidiHost::IsValidWebMIDIData(buffer));
}
// MIDI realtime message can be placed at any position.
{
const uint8_t kNoteOnWithRealTimeClock[] = {
0x90, 0xf8, 0x3c, 0x7f, 0x90, 0xf8, 0x3c, 0xf8, 0x7f, 0xf8,
};
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(
AsVector(kNoteOnWithRealTimeClock)));
const uint8_t kGMOnWithRealTimeClock[] = {
0xf0, 0xf8, 0x7e, 0x7f, 0x09, 0x01, 0xf8, 0xf7,
};
EXPECT_TRUE(MidiHost::IsValidWebMIDIData(
AsVector(kGMOnWithRealTimeClock)));
}
}
// Test if sending data to out of range port is ignored.
TEST_F(MidiHostTest, OutputPortCheck) {
// Only one output port is available.
AddOutputPort();
// Sending data to port 0 should be delivered.
uint32_t port0 = 0;
OnSendData(port0);
RunLoopUntilIdle();
EXPECT_EQ(1U, GetEventSize());
CheckSendEventAt(0, port0);
// Sending data to port 1 should not be delivered.
uint32_t port1 = 1;
OnSendData(port1);
RunLoopUntilIdle();
EXPECT_EQ(1U, GetEventSize());
// Two output ports are available from now on.
AddOutputPort();
// Sending data to port 0 and 1 should be delivered now.
OnSendData(port0);
OnSendData(port1);
RunLoopUntilIdle();
EXPECT_EQ(3U, GetEventSize());
CheckSendEventAt(1, port0);
CheckSendEventAt(2, port1);
}
} // namespace conent