1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16#include "host/libs/usbip/client.h"
17
18#include <arpa/inet.h>
19
20#include <glog/logging.h>
21#include <iostream>
22
23#include "host/libs/usbip/device.h"
24#include "host/libs/usbip/messages.h"
25
26namespace vadb {
27namespace usbip {
28
29// NetToHost and HostToNet are used to reduce risk of copy/paste errors and to
30// provide uniform method of converting messages between different endian types.
31namespace {
32
33uint32_t NetToHost(uint32_t t) { return ntohl(t); }
34
35Command NetToHost(Command t) { return static_cast<Command>(ntohl(t)); }
36
37Direction NetToHost(Direction t) { return static_cast<Direction>(ntohl(t)); }
38
39uint32_t NetToHost(uint16_t t) { return ntohs(t); }
40
41CmdHeader NetToHost(const CmdHeader& t) {
42  CmdHeader rval = t;
43  rval.command = NetToHost(t.command);
44  rval.seq_num = NetToHost(t.seq_num);
45  rval.bus_num = NetToHost(t.bus_num);
46  rval.dev_num = NetToHost(t.dev_num);
47  rval.direction = NetToHost(t.direction);
48  rval.endpoint = NetToHost(t.endpoint);
49  return rval;
50}
51
52CmdReqSubmit NetToHost(const CmdReqSubmit& t) {
53  CmdReqSubmit rval = t;
54  rval.transfer_flags = NetToHost(t.transfer_flags);
55  rval.transfer_buffer_length = NetToHost(t.transfer_buffer_length);
56  rval.start_frame = NetToHost(t.start_frame);
57  rval.number_of_packets = NetToHost(t.number_of_packets);
58  rval.deadline_interval = NetToHost(t.deadline_interval);
59  return rval;
60}
61
62CmdReqUnlink NetToHost(const CmdReqUnlink& t) {
63  CmdReqUnlink rval = t;
64  rval.seq_num = NetToHost(t.seq_num);
65  return rval;
66}
67
68uint32_t HostToNet(uint32_t t) { return htonl(t); }
69
70Command HostToNet(const Command t) { return static_cast<Command>(htonl(t)); }
71
72Direction HostToNet(Direction t) { return static_cast<Direction>(htonl(t)); }
73
74uint16_t HostToNet(uint16_t t) { return htons(t); }
75
76CmdHeader HostToNet(const CmdHeader& t) {
77  CmdHeader rval = t;
78  rval.command = HostToNet(t.command);
79  rval.seq_num = HostToNet(t.seq_num);
80  rval.bus_num = HostToNet(t.bus_num);
81  rval.dev_num = HostToNet(t.dev_num);
82  rval.direction = HostToNet(t.direction);
83  rval.endpoint = HostToNet(t.endpoint);
84  return rval;
85}
86
87CmdRepSubmit HostToNet(const CmdRepSubmit& t) {
88  CmdRepSubmit rval = t;
89  rval.status = HostToNet(t.status);
90  rval.actual_length = HostToNet(t.actual_length);
91  rval.start_frame = HostToNet(t.start_frame);
92  rval.number_of_packets = HostToNet(t.number_of_packets);
93  rval.error_count = HostToNet(t.error_count);
94  return rval;
95}
96
97CmdRepUnlink HostToNet(const CmdRepUnlink& t) {
98  CmdRepUnlink rval = t;
99  rval.status = HostToNet(t.status);
100  return rval;
101}
102
103// Converts data to network order and sends it to the USB/IP client.
104// Returns true, if message was sent successfully.
105template <typename T>
106bool SendUSBIPMsg(const cvd::SharedFD& fd, const T& data) {
107  T net = HostToNet(data);
108  return fd->Send(&net, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
109}
110
111// Receive message from USB/IP client.
112// After message is received, it's updated to match host endian.
113// Returns true, if message was received successfully.
114template <typename T>
115bool RecvUSBIPMsg(const cvd::SharedFD& fd, T* data) {
116  T net;
117  bool res = fd->Recv(&net, sizeof(T), MSG_NOSIGNAL) == sizeof(T);
118  if (res) {
119    *data = NetToHost(net);
120  }
121  return res;
122}
123
124}  // namespace
125
126void Client::BeforeSelect(cvd::SharedFDSet* fd_read) const {
127  fd_read->Set(fd_);
128}
129
130bool Client::AfterSelect(const cvd::SharedFDSet& fd_read) {
131  if (fd_read.IsSet(fd_)) return HandleIncomingMessage();
132  return true;
133}
134
135// Handle incoming COMMAND.
136//
137// Read next CMD from client channel.
138// Returns false, if connection should be dropped.
139bool Client::HandleIncomingMessage() {
140  CmdHeader hdr;
141  if (!RecvUSBIPMsg(fd_, &hdr)) {
142    LOG(ERROR) << "Could not read command header: " << fd_->StrError();
143    return false;
144  }
145
146  // And the protocol, again.
147  switch (hdr.command) {
148    case kUsbIpCmdReqSubmit:
149      return HandleSubmitCmd(hdr);
150
151    case kUsbIpCmdReqUnlink:
152      return HandleUnlinkCmd(hdr);
153
154    default:
155      LOG(ERROR) << "Unsupported command requested: " << hdr.command;
156      return false;
157  }
158}
159
160// Handle incoming SUBMIT COMMAND.
161//
162// Execute command on specified USB device.
163// Returns false, if connection should be dropped.
164bool Client::HandleSubmitCmd(const CmdHeader& cmd) {
165  CmdReqSubmit req;
166  if (!RecvUSBIPMsg(fd_, &req)) {
167    LOG(ERROR) << "Could not read submit command: " << fd_->StrError();
168    return false;
169  }
170
171  uint32_t seq_num = cmd.seq_num;
172
173  // Reserve buffer for data in or out.
174  std::vector<uint8_t> payload;
175  int payload_length = req.transfer_buffer_length;
176  payload.resize(payload_length);
177
178  bool is_host_to_device = cmd.direction == kUsbIpDirectionOut;
179  // Control requests are quite easy to detect; if setup is all '0's, then we're
180  // doing a data transfer, otherwise it's a control transfer.
181  // We only check for cmd and type fields here, as combination 0/0 of these
182  // fields is already invalid (cmd == GET_STATUS, type = WRITE).
183  bool is_control_request = !(req.setup.cmd == 0 && req.setup.type == 0);
184
185  // Find requested device and execute command.
186  auto device = pool_.GetDevice({cmd.bus_num, cmd.dev_num});
187  if (device) {
188    // Read data to be sent to device, if specified.
189    if (is_host_to_device && payload_length) {
190      size_t got = 0;
191      // Make sure we read everything.
192      while (got < payload.size()) {
193        auto read =
194            fd_->Recv(&payload[got], payload.size() - got, MSG_NOSIGNAL);
195        if (fd_->GetErrno() != 0) {
196          LOG(ERROR) << "Client disconnected: " << fd_->StrError();
197          return false;
198        } else if (!read) {
199          LOG(ERROR) << "Short read; client likely disconnected.";
200          return false;
201        }
202        got += read;
203      }
204    }
205
206    // If setup structure of request is initialized then we need to execute
207    // control transfer. Otherwise, this is a plain data exchange.
208    bool send_success = false;
209    if (is_control_request) {
210      send_success = device->handle_control_transfer(
211          req.setup, req.deadline_interval, std::move(payload),
212          [this, seq_num, is_host_to_device](bool is_success,
213                                             std::vector<uint8_t> data) {
214            HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
215                                 std::move(data));
216          });
217    } else {
218      send_success = device->handle_data_transfer(
219          cmd.endpoint, is_host_to_device, req.deadline_interval,
220          std::move(payload),
221          [this, seq_num, is_host_to_device](bool is_success,
222                                             std::vector<uint8_t> data) {
223            HandleAsyncDataReady(seq_num, is_success, is_host_to_device,
224                                 std::move(data));
225          });
226    }
227
228    // Simply fail if couldn't execute command.
229    if (!send_success) {
230      HandleAsyncDataReady(seq_num, false, is_host_to_device,
231                           std::vector<uint8_t>());
232    }
233  }
234  return true;
235}
236
237void Client::HandleAsyncDataReady(uint32_t seq_num, bool is_success,
238                                  bool is_host_to_device,
239                                  std::vector<uint8_t> data) {
240  // Response template.
241  // - in header, host doesn't care about anything else except for command type
242  //   and sequence number.
243  // - in body, report status == !OK unless we completed everything
244  //   successfully.
245  CmdHeader rephdr{};
246  rephdr.command = kUsbIpCmdRepSubmit;
247  rephdr.seq_num = seq_num;
248
249  CmdRepSubmit rep{};
250  rep.status = is_success ? 0 : 1;
251  rep.actual_length = data.size();
252
253  // Data out.
254  if (!SendUSBIPMsg(fd_, rephdr)) {
255    LOG(ERROR) << "Failed to send response header: " << fd_->StrError();
256    return;
257  }
258
259  if (!SendUSBIPMsg(fd_, rep)) {
260    LOG(ERROR) << "Failed to send response body: " << fd_->StrError();
261    return;
262  }
263
264  if (!is_host_to_device && data.size() > 0) {
265    if (static_cast<size_t>(
266            fd_->Send(data.data(), data.size(), MSG_NOSIGNAL)) != data.size()) {
267      LOG(ERROR) << "Failed to send response payload: " << fd_->StrError();
268      return;
269    }
270  }
271}
272
273// Handle incoming UNLINK COMMAND.
274//
275// Unlink removes command specified via seq_num from a list of commands to be
276// executed.
277// We don't schedule commands for execution, so technically every UNLINK will
278// come in late.
279// Returns false, if connection should be dropped.
280bool Client::HandleUnlinkCmd(const CmdHeader& cmd) {
281  CmdReqUnlink req;
282  if (!RecvUSBIPMsg(fd_, &req)) {
283    LOG(ERROR) << "Could not read unlink command: " << fd_->StrError();
284    return false;
285  }
286  LOG(INFO) << "Client requested to unlink previously submitted command: "
287            << req.seq_num;
288
289  CmdHeader rephdr{};
290  rephdr.command = kUsbIpCmdRepUnlink;
291  rephdr.seq_num = cmd.seq_num;
292
293  // Technically we do not schedule commands for execution, so we cannot
294  // de-queue commands, either. Indicate this by sending status != ok.
295  CmdRepUnlink rep;
296  rep.status = 1;
297
298  if (!SendUSBIPMsg(fd_, rephdr)) {
299    LOG(ERROR) << "Could not send unlink command header: " << fd_->StrError();
300    return false;
301  }
302
303  if (!SendUSBIPMsg(fd_, rep)) {
304    LOG(ERROR) << "Could not send unlink command data: " << fd_->StrError();
305    return false;
306  }
307  return true;
308}
309
310}  // namespace usbip
311}  // namespace vadb
312