| // Copyright 2022 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef SRC_UI_INPUT_DRIVERS_CTAPHID_CTAPHID_H_ |
| #define SRC_UI_INPUT_DRIVERS_CTAPHID_CTAPHID_H_ |
| |
| #include <fidl/fuchsia.fido.report/cpp/wire.h> |
| #include <fuchsia/hardware/hiddevice/cpp/banjo.h> |
| #include <lib/async-loop/cpp/loop.h> |
| #include <lib/async-loop/default.h> |
| |
| #include <map> |
| |
| #include <ddktl/device.h> |
| #include <ddktl/protocol/empty-protocol.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/mutex.h> |
| |
| namespace ctaphid { |
| using channel_id_t = uint32_t; |
| using command_id_t = uint8_t; |
| using GetMessageCompleter = |
| ::fidl::internal::WireCompleter<::fuchsia_fido_report::SecurityKeyDevice::GetMessage>; |
| |
| // The following are CTAPHID error codes from the CTAP specification v2.1-ps-20210615 |
| // section 11.2.9.1.6. |
| enum CtaphidErr { |
| InvalidCmd = 0x01, // The command in the request is invalid |
| InvalidPar = 0x02, // The parameter(s) in the request is invalid |
| InvalidLen = 0x03, // The length field (BCNT) is invalid for the request |
| InvalidSeq = 0x04, // The sequence does not match expected value |
| MsgTimeout = 0x05, // The message has timed out |
| ChannelBusy = |
| 0x06, // The device is busy for the requesting channel. The client SHOULD retry the request |
| // after a short delay. Note that the client MAY abort the transaction if the command |
| // is no longer relevant. |
| LockRequired = 0x0A, // Command requires channel lock |
| InvalidChannel = 0x0B, // CID is not valid. |
| Other = 0x7F, // Unspecified error |
| }; |
| |
| using pending_response = struct pending_response { |
| // The channel we are waiting on a response from. |
| channel_id_t channel; |
| |
| // The fields to be set by the response. |
| std::optional<command_id_t> command; |
| // This is also the expected number of bytes to be received for the current response. |
| std::optional<uint16_t> payload_len; |
| std::vector<uint8_t> data; |
| |
| // Keep track of the number of bytes received so far for the response. |
| uint16_t bytes_received = 0; |
| |
| // The time the last packet of this response was received. |
| std::optional<zx_time_t> last_packet_received_time; |
| // The next expected sequence value of a continuation packet. |
| uint8_t next_packet_seq_expected; |
| |
| // Keeps a reference to a pending request if GetMessage is called on this channel before |
| // the response has been sent from the key. |
| std::optional<GetMessageCompleter::Async> waiting_read; |
| }; |
| |
| class CtapHidDriver; |
| using CtapHidDriverDeviceType = |
| ddk::Device<CtapHidDriver, ddk::Unbindable, |
| ddk::Messageable<fuchsia_fido_report::SecurityKeyDevice>::Mixin>; |
| class CtapHidDriver : public CtapHidDriverDeviceType, |
| public ddk::EmptyProtocol<ZX_PROTOCOL_CTAP>, |
| ddk::HidReportListenerProtocol<CtapHidDriver> { |
| public: |
| CtapHidDriver(zx_device_t* parent, ddk::HidDeviceProtocolClient hiddev) |
| : CtapHidDriverDeviceType(parent), hiddev_(hiddev) {} |
| |
| ~CtapHidDriver() { |
| fbl::AutoLock lock(&lock_); |
| if (pending_response_ && pending_response_->waiting_read) { |
| pending_response_->waiting_read->ReplyError(ZX_ERR_PEER_CLOSED); |
| pending_response_->waiting_read.reset(); |
| } |
| } |
| |
| zx_status_t Start(); |
| void Stop(); |
| |
| // DDK Functions. |
| zx_status_t Bind(); |
| void DdkUnbind(ddk::UnbindTxn txn); |
| void DdkRelease(); |
| |
| // FIDL functions. |
| void SendMessage(SendMessageRequestView request, SendMessageCompleter::Sync& completer) override; |
| void GetMessage(GetMessageRequestView request, GetMessageCompleter::Sync& completer) override; |
| |
| // HidReportListener functions. |
| void HidReportListenerReceiveReport(const uint8_t* report, size_t report_size, |
| zx_time_t report_time); |
| |
| private: |
| static constexpr size_t kFidlReportBufferSize = 8192; |
| |
| // The index of the first byte of the payload in an initialization packet. |
| static constexpr uint8_t INITIALIZATION_PAYLOAD_DATA_OFFSET = 7; |
| // The index of the first byte of the payload in a continuation packet. |
| static constexpr uint8_t CONTINUATION_PAYLOAD_DATA_OFFSET = 5; |
| // The maximum number of packets a payload can be divided into, as per the CTAP spec. |
| static constexpr uint8_t MIN_PACKET_SEQ = 0x00; |
| static constexpr uint8_t MAX_PACKET_SEQ = 0x7f; |
| // The first packet send to a device follows the structure of an initialization packet. |
| static constexpr uint8_t INIT_PACKET_SEQ = 0xff; |
| // MSB is set for the 5th byte of Initialisation packets. |
| static constexpr uint8_t INIT_PACKET_BIT = (1u << 7); |
| // Indices of the remaining packet fields. |
| static constexpr uint8_t CHANNEL_ID_OFFSET = 0; |
| static constexpr uint8_t COMMAND_ID_OFFSET = 4; |
| static constexpr uint8_t PACKET_SEQ_OFFSET = 4; |
| static constexpr uint8_t PAYLOAD_LEN_HI_OFFSET = 5; |
| static constexpr uint8_t PAYLOAD_LEN_LO_OFFSET = 6; |
| |
| void ReplyToWaitingGetMessage() __TA_REQUIRES(lock_); |
| |
| void CreatePacketHeader(uint8_t packet_sequence, uint32_t channel_id, |
| fuchsia_fido_report::CtapHidCommand command_id, uint16_t payload_len, |
| uint8_t* out, size_t out_size); |
| |
| ddk::HidDeviceProtocolClient hiddev_; |
| |
| fbl::Mutex lock_; |
| fidl::Arena<kFidlReportBufferSize> response_allocator_ __TA_GUARDED(lock_); |
| |
| // Fields for the output packets to be received from devices. |
| uint8_t output_packet_id_ = 0; |
| size_t output_packet_size_ = 0; |
| size_t max_output_data_size_ = 0; |
| |
| // Currently awaiting response. |
| std::optional<pending_response> pending_response_ __TA_GUARDED(lock_); |
| }; |
| |
| } // namespace ctaphid |
| |
| #endif // SRC_UI_INPUT_DRIVERS_CTAPHID_CTAPHID_H_ |