blob: 0997acc015260bb31f2f031dab808733e8d9bd35 [file] [log] [blame]
// Copyright 2017 The Chromium OS 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 <memory>
#include <base/files/file_descriptor_watcher_posix.h>
#include <base/memory/weak_ptr.h>
#include <gtest/gtest_prod.h>
#include "midis/device.h"
#include "midis/device_tracker.h"
namespace midis {
class DeviceTracker;
// Class to handle all interactions with the ALSA sequencer interface.
// NOTE: The term "input" refers to data received *from* the MIDI H/W and
// external clients that are registered to the ALSA sequencer interfance. The
// term "output" refers to data that a client of midis write *to* MIDI H/W and
// external clients.
class SeqHandler {
using AddDeviceCallback = base::Callback<void(std::unique_ptr<Device>)>;
using RemoveDeviceCallback = base::Callback<void(uint32_t, uint32_t)>;
using HandleReceiveDataCallback =
base::Callback<void(uint32_t, uint32_t, uint32_t, const char*, size_t)>;
using IsDevicePresentCallback = base::Callback<bool(uint32_t, uint32_t)>;
using IsPortPresentCallback =
base::Callback<bool(uint32_t, uint32_t, uint32_t)>;
struct SeqDeleter {
void operator()(snd_seq_t* seq) const { snd_seq_close(seq); }
struct MidiEventDeleter {
void operator()(snd_midi_event_t* coder) const {
using ScopedMidiEventPtr =
std::unique_ptr<snd_midi_event_t, MidiEventDeleter>;
using ScopedSeqPtr = std::unique_ptr<snd_seq_t, SeqDeleter>;
SeqHandler(AddDeviceCallback add_device_cb,
RemoveDeviceCallback remove_device_cb,
HandleReceiveDataCallback handle_rx_data_cb,
IsDevicePresentCallback is_device_present_cb,
IsPortPresentCallback is_port_present_cb);
virtual ~SeqHandler() = default;
// Initializes the ALSA seq interface. Creates client handles for input and
// output, as well create an input port to receive messages (announce as
// well as MIDI data) from ALSA seq. Also starts off the file watcher which
// watches for events on the input port.
bool InitSeq();
void ProcessAlsaClientFd();
// Creates a Device object and runs the necessary callback to register that
// object with DeviceTracker, stored in |add_device_cb_|
virtual void AddSeqDevice(uint32_t device_id);
// At present, we don't support hotplugging of individual ports in devices.
// so, we enumerate all the available ports in AddAlsaDevice().
// This function is here merely to handle MIDI events associated with any
// port being added or removed later (and to print an error message, since
// we don't support it yet)
virtual void AddSeqPort(uint32_t device_id, uint32_t port_id);
// Runs the relevant callback, stored in |remove_device_cb_|, when a MIDI H/W
// device or external client is removed from the ALSA sequencer interface.
virtual void RemoveSeqDevice(uint32_t device_id);
virtual void RemoveSeqPort(uint32_t device_id, uint32_t port_id);
// Callback to run when starting an input port (establishes a subscription,
// and creates a relevant port on the server side, in necessary).
// Returns true on success, false otherwise.
bool SubscribeInPort(uint32_t device_id, uint32_t port_id);
// Callback to run when starting an input port (establishes a subscription,
// and creates a relevant port on the server side, in necessary).
// Returns created seq port id success, -1 otherwise.
int SubscribeOutPort(uint32_t device_id, uint32_t port_id);
// The following two functions undo the work of the Subscribe*Port() function,
// for input and output ports respectively.
void UnsubscribeInPort(uint32_t device_id, uint32_t port_id);
void UnsubscribeOutPort(int out_port_id);
// Encodes the bytes in a MIDI buffer into the provided |encoder|.
bool EncodeMidiBytes(int out_port_id,
snd_seq_t* out_client,
const uint8_t* buffer,
size_t buffer_len,
snd_midi_event_t* encoder);
// Callback to send MIDI data to the H/W. This callback is generally called by
// a Device handler which receives MIDI data from a client (e.g ARC++). The
// Device handler will in turn be called by a Client handler which is
// listening for data from it's client.
void SendMidiData(int out_port_id, const uint8_t* buffer, size_t buf_len);
// This function processes the MIDI data received from H/W or an external
// client, and invokes the callback |handle_rx_data_cb_| which handles the
// data accordingly.
virtual void ProcessMidiEvent(snd_seq_event_t* event);
// Wrappers for functions that interact with the ALSA Sequencer interface.
// These are kept separately, because the intention is to mock these functions
// in unit tests.
virtual int SndSeqEventOutputDirect(snd_seq_t* out_client,
snd_seq_event_t* event);
virtual int SndSeqEventInput(snd_seq_t* in_client, snd_seq_event_t** ev);
virtual int SndSeqEventInputPending(snd_seq_t* in_client,
int fetch_sequencer);
// For testing purposes.
SeqHandler(const SeqHandler&) = delete;
SeqHandler& operator=(const SeqHandler&) = delete;
friend class SeqHandlerTest;
friend class SeqHandlerFuzzer;
FRIEND_TEST(SeqHandlerTest, TestEncodeBytes);
FRIEND_TEST(SeqHandlerTest, TestProcessAlsaClientFdPositive);
FRIEND_TEST(SeqHandlerTest, TestProcessMidiEventsPositive);
FRIEND_TEST(SeqHandlerTest, TestProcessMidiEventsNegative);
// Enumerates all clients which are already connected to the ALSA Sequencer.
void EnumerateExistingDevices();
// Creates a MIDI event wrapped in a ScopedMidiEventPtr.
static ScopedMidiEventPtr CreateMidiEvent(size_t buf_size);
std::unique_ptr<snd_seq_t, SeqDeleter> in_client_;
std::unique_ptr<snd_seq_t, SeqDeleter> out_client_;
std::unique_ptr<snd_midi_event_t, MidiEventDeleter> decoder_;
int in_client_id_;
int out_client_id_;
int in_port_id_;
std::unique_ptr<pollfd> pfd_;
std::unique_ptr<base::FileDescriptorWatcher::Controller> watcher_;
AddDeviceCallback add_device_cb_;
RemoveDeviceCallback remove_device_cb_;
HandleReceiveDataCallback handle_rx_data_cb_;
IsDevicePresentCallback is_device_present_cb_;
IsPortPresentCallback is_port_present_cb_;
base::WeakPtrFactory<SeqHandler> weak_factory_;
} // namespace midis