blob: 50ea9f8fdc0446ddd0fc0b80a32df51e5e5d626d [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "host/commands/virtual_usb_manager/usbip/client.h"
#include <arpa/inet.h>
#include <glog/logging.h>
#include <iostream>
#include "host/commands/virtual_usb_manager/usbip/device.h"
#include "host/commands/virtual_usb_manager/usbip/messages.h"
namespace vadb {
namespace usbip {
// NetToHost and HostToNet are used to reduce risk of copy/paste errors and to
// provide uniform method of converting messages between different endian types.
namespace {
uint32_t NetToHost(uint32_t t) { return ntohl(t); }
Command NetToHost(Command t) { return static_cast<Command>(ntohl(t)); }
Direction NetToHost(Direction t) { return static_cast<Direction>(ntohl(t)); }
uint32_t NetToHost(uint16_t t) { return ntohs(t); }
CmdHeader NetToHost(const CmdHeader& t) {
CmdHeader rval = t;
rval.command = NetToHost(t.command);
rval.seq_num = NetToHost(t.seq_num);
rval.bus_num = NetToHost(t.bus_num);
rval.dev_num = NetToHost(t.dev_num);
rval.direction = NetToHost(t.direction);
rval.endpoint = NetToHost(t.endpoint);
return rval;
}
CmdReqSubmit NetToHost(const CmdReqSubmit& t) {
CmdReqSubmit rval = t;
rval.transfer_flags = NetToHost(t.transfer_flags);
rval.transfer_buffer_length = NetToHost(t.transfer_buffer_length);
rval.start_frame = NetToHost(t.start_frame);
rval.number_of_packets = NetToHost(t.number_of_packets);
rval.deadline_interval = NetToHost(t.deadline_interval);
return rval;
}
CmdReqUnlink NetToHost(const CmdReqUnlink& t) {
CmdReqUnlink rval = t;
rval.seq_num = NetToHost(t.seq_num);
return rval;
}
uint32_t HostToNet(uint32_t t) { return htonl(t); }
Command HostToNet(const Command t) { return static_cast<Command>(htonl(t)); }
Direction HostToNet(Direction t) { return static_cast<Direction>(htonl(t)); }
uint16_t HostToNet(uint16_t t) { return htons(t); }
CmdHeader HostToNet(const CmdHeader& t) {
CmdHeader rval = t;
rval.command = HostToNet(t.command);
rval.seq_num = HostToNet(t.seq_num);
rval.bus_num = HostToNet(t.bus_num);
rval.dev_num = HostToNet(t.dev_num);
rval.direction = HostToNet(t.direction);
rval.endpoint = HostToNet(t.endpoint);
return rval;
}
CmdRepSubmit HostToNet(const CmdRepSubmit& t) {
CmdRepSubmit rval = t;
rval.status = HostToNet(t.status);
rval.actual_length = HostToNet(t.actual_length);
rval.start_frame = HostToNet(t.start_frame);
rval.number_of_packets = HostToNet(t.number_of_packets);
rval.error_count = HostToNet(t.error_count);
return rval;
}
CmdRepUnlink HostToNet(const CmdRepUnlink& t) {
CmdRepUnlink rval = t;
rval.status = HostToNet(t.status);
return rval;
}
// Converts data to network order and sends it to the USB/IP client.
// Returns true, if message was sent successfully.
template <typename T>
bool SendUSBIPMsg(const cvd::SharedFD& fd, const T& data) {
T net = HostToNet(data);
return fd->Send(&net, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
}
// Receive message from USB/IP client.
// After message is received, it's updated to match host endian.
// Returns true, if message was received successfully.
template <typename T>
bool RecvUSBIPMsg(const cvd::SharedFD& fd, T* data) {
T net;
bool res = fd->Recv(&net, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
if (res) {
*data = NetToHost(net);
}
return res;
}
} // namespace
void Client::BeforeSelect(cvd::SharedFDSet* fd_read) const {
fd_read->Set(fd_);
}
bool Client::AfterSelect(const cvd::SharedFDSet& fd_read) {
if (fd_read.IsSet(fd_)) return HandleIncomingMessage();
return true;
}
// Handle incoming COMMAND.
//
// Read next CMD from client channel.
// Returns false, if connection should be dropped.
bool Client::HandleIncomingMessage() {
CmdHeader hdr;
if (!RecvUSBIPMsg(fd_, &hdr)) {
LOG(ERROR) << "Could not read command header: " << fd_->StrError();
return false;
}
// And the protocol, again.
switch (hdr.command) {
case kUsbIpCmdReqSubmit:
return HandleSubmitCmd(hdr);
case kUsbIpCmdReqUnlink:
return HandleUnlinkCmd(hdr);
default:
LOG(ERROR) << "Unsupported command requested: " << hdr.command;
return false;
}
}
// Handle incoming SUBMIT COMMAND.
//
// Execute command on specified USB device.
// Returns false, if connection should be dropped.
bool Client::HandleSubmitCmd(const CmdHeader& cmd) {
CmdReqSubmit req;
if (!RecvUSBIPMsg(fd_, &req)) {
LOG(ERROR) << "Could not read submit command: " << fd_->StrError();
return false;
}
uint32_t seq_num = cmd.seq_num;
// Reserve buffer for data in or out.
std::vector<uint8_t> payload;
int payload_length = req.transfer_buffer_length;
payload.resize(payload_length);
bool is_host_to_device = cmd.direction == kUsbIpDirectionOut;
// Control requests are quite easy to detect; if setup is all '0's, then we're
// doing a data transfer, otherwise it's a control transfer.
// We only check for cmd and type fields here, as combination 0/0 of these
// fields is already invalid (cmd == GET_STATUS, type = WRITE).
bool is_control_request = !(req.setup.cmd == 0 && req.setup.type == 0);
// Find requested device and execute command.
auto device = pool_.GetDevice({cmd.bus_num, cmd.dev_num});
if (device) {
// Read data to be sent to device, if specified.
if (is_host_to_device && payload_length) {
size_t got = 0;
// Make sure we read everything.
while (got < payload.size()) {
auto read =
fd_->Recv(&payload[got], payload.size() - got, MSG_NOSIGNAL);
if (fd_->GetErrno() != 0) {
LOG(ERROR) << "Client disconnected: " << fd_->StrError();
return false;
} else if (!read) {
LOG(ERROR) << "Short read; client likely disconnected.";
return false;
}
got += read;
}
}
// If setup structure of request is initialized then we need to execute
// control transfer. Otherwise, this is a plain data exchange.
bool send_success = false;
if (is_control_request) {
send_success = device->handle_control_transfer(
req.setup, req.deadline_interval, std::move(payload),
[this, seq_num, is_host_to_device](bool is_success,
std::vector<uint8_t> data) {
HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
std::move(data));
});
} else {
send_success = device->handle_data_transfer(
cmd.endpoint, is_host_to_device, req.deadline_interval,
std::move(payload),
[this, seq_num, is_host_to_device](bool is_success,
std::vector<uint8_t> data) {
HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
std::move(data));
});
}
// Simply fail if couldn't execute command.
if (!send_success) {
HandleAsyncDataReady(seq_num, false, is_host_to_device,
std::vector<uint8_t>());
}
}
return true;
}
void Client::HandleAsyncDataReady(uint32_t seq_num, bool is_success,
bool is_host_to_device,
std::vector<uint8_t> data) {
// Response template.
// - in header, host doesn't care about anything else except for command type
// and sequence number.
// - in body, report status == !OK unless we completed everything
// successfully.
CmdHeader rephdr{};
rephdr.command = kUsbIpCmdRepSubmit;
rephdr.seq_num = seq_num;
CmdRepSubmit rep{};
rep.status = is_success ? 0 : 1;
rep.actual_length = data.size();
// Data out.
if (!SendUSBIPMsg(fd_, rephdr)) {
LOG(ERROR) << "Failed to send response header: " << fd_->StrError();
return;
}
if (!SendUSBIPMsg(fd_, rep)) {
LOG(ERROR) << "Failed to send response body: " << fd_->StrError();
return;
}
if (!is_host_to_device && data.size() > 0) {
if (static_cast<size_t>(
fd_->Send(data.data(), data.size(), MSG_NOSIGNAL)) != data.size()) {
LOG(ERROR) << "Failed to send response payload: " << fd_->StrError();
return;
}
}
}
// Handle incoming UNLINK COMMAND.
//
// Unlink removes command specified via seq_num from a list of commands to be
// executed.
// We don't schedule commands for execution, so technically every UNLINK will
// come in late.
// Returns false, if connection should be dropped.
bool Client::HandleUnlinkCmd(const CmdHeader& cmd) {
CmdReqUnlink req;
if (!RecvUSBIPMsg(fd_, &req)) {
LOG(ERROR) << "Could not read unlink command: " << fd_->StrError();
return false;
}
LOG(INFO) << "Client requested to unlink previously submitted command: "
<< req.seq_num;
CmdHeader rephdr{};
rephdr.command = kUsbIpCmdRepUnlink;
rephdr.seq_num = cmd.seq_num;
// Technically we do not schedule commands for execution, so we cannot
// de-queue commands, either. Indicate this by sending status != ok.
CmdRepUnlink rep;
rep.status = 1;
if (!SendUSBIPMsg(fd_, rephdr)) {
LOG(ERROR) << "Could not send unlink command header: " << fd_->StrError();
return false;
}
if (!SendUSBIPMsg(fd_, rep)) {
LOG(ERROR) << "Could not send unlink command data: " << fd_->StrError();
return false;
}
return true;
}
} // namespace usbip
} // namespace vadb