| // Copyright 2016 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 "biod/fake_biometric.h" |
| |
| #include <fcntl.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| |
| #include <algorithm> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/logging.h> |
| |
| namespace biod { |
| |
| uint64_t FakeBiometric::Enrollment::GetId() const { |
| return static_cast<uint64_t>(id_); |
| } |
| |
| const std::string& FakeBiometric::Enrollment::GetUserId() const { |
| InternalEnrollment* internal = GetInternal(); |
| CHECK(internal) |
| << "Attempted to get user ID for invalid Biometric Enrollment"; |
| return internal->user_id; |
| } |
| |
| const std::string& FakeBiometric::Enrollment::GetLabel() const { |
| InternalEnrollment* internal = GetInternal(); |
| CHECK(internal) << "Attempted to get label for invalid Biometric Enrollment"; |
| return internal->label; |
| } |
| |
| bool FakeBiometric::Enrollment::SetLabel(std::string label) { |
| InternalEnrollment* internal = GetInternal(); |
| if (internal) { |
| internal->label = std::move(label); |
| return true; |
| } |
| LOG(ERROR) << "Attempt to set label for invalid Biometric Enrollment"; |
| return false; |
| } |
| |
| bool FakeBiometric::Enrollment::Remove() { |
| return biometric_->enrollments_.erase(id_) > 0; |
| } |
| |
| FakeBiometric::InternalEnrollment* FakeBiometric::Enrollment::GetInternal() |
| const { |
| if (!biometric_) |
| return nullptr; |
| auto internal_enrollment = biometric_->enrollments_.find(id_); |
| if (internal_enrollment == biometric_->enrollments_.end()) |
| return nullptr; |
| return &internal_enrollment->second; |
| } |
| |
| FakeBiometric::FakeBiometric() |
| : session_weak_factory_(this), weak_factory_(this) { |
| const char kFakeInputPath[] = "/tmp/fake_biometric"; |
| base::DeleteFile(base::FilePath(kFakeInputPath), false); |
| CHECK_EQ(mkfifo(kFakeInputPath, 0600), 0) |
| << "Failed to create fake biometric input"; |
| // The pipe gets opened read/write to avoid triggering a constant stream of |
| // POLLHUP after the pipe is opened writable and closed. The pipe is never |
| // actually written to here. |
| fake_input_ = base::ScopedFD(open(kFakeInputPath, O_RDWR | O_NONBLOCK)); |
| CHECK_GE(fake_input_.get(), 0) << "Failed to open fake biometric input"; |
| |
| fd_watcher_.reset(new base::MessageLoopForIO::FileDescriptorWatcher); |
| |
| CHECK(base::MessageLoopForIO::current()->WatchFileDescriptor( |
| fake_input_.get(), |
| true, |
| base::MessageLoopForIO::WATCH_READ, |
| fd_watcher_.get(), |
| this)) |
| << "Failed to watch fake biometric input"; |
| } |
| |
| Biometric::Type FakeBiometric::GetType() { |
| return Biometric::Type::kFingerprint; |
| } |
| |
| Biometric::EnrollSession FakeBiometric::StartEnroll(std::string user_id, |
| std::string label) { |
| if (mode_ == Mode::kNone) { |
| mode_ = Mode::kEnroll; |
| next_internal_enrollment_ = {std::move(user_id), std::move(label)}; |
| return Biometric::EnrollSession(session_weak_factory_.GetWeakPtr()); |
| } |
| return Biometric::EnrollSession(); |
| } |
| |
| Biometric::AuthenticationSession FakeBiometric::StartAuthentication() { |
| if (mode_ == Mode::kNone) { |
| mode_ = Mode::kAuthentication; |
| return Biometric::AuthenticationSession(session_weak_factory_.GetWeakPtr()); |
| } |
| return Biometric::AuthenticationSession(); |
| } |
| |
| std::vector<std::unique_ptr<Biometric::Enrollment>> |
| FakeBiometric::GetEnrollments() { |
| std::vector<std::unique_ptr<Biometric::Enrollment>> enrollments( |
| enrollments_.size()); |
| std::transform(enrollments_.begin(), |
| enrollments_.end(), |
| enrollments.begin(), |
| [this](decltype(enrollments_)::value_type& enrollment) { |
| return std::unique_ptr<Biometric::Enrollment>(new Enrollment( |
| weak_factory_.GetWeakPtr(), enrollment.first)); |
| }); |
| return enrollments; |
| } |
| |
| bool FakeBiometric::DestroyAllEnrollments() { |
| enrollments_.clear(); |
| return true; |
| } |
| |
| void FakeBiometric::SetScannedHandler(const Biometric::ScanCallback& on_scan) { |
| on_scan_ = on_scan; |
| } |
| |
| void FakeBiometric::SetAttemptHandler( |
| const Biometric::AttemptCallback& on_attempt) { |
| on_attempt_ = on_attempt; |
| } |
| |
| void FakeBiometric::SetFailureHandler( |
| const Biometric::FailureCallback& on_failure) { |
| on_failure_ = on_failure; |
| } |
| |
| void FakeBiometric::EndEnroll() { |
| CHECK(mode_ == Mode::kEnroll); |
| session_weak_factory_.InvalidateWeakPtrs(); |
| mode_ = Mode::kNone; |
| } |
| |
| void FakeBiometric::EndAuthentication() { |
| CHECK(mode_ == Mode::kAuthentication); |
| session_weak_factory_.InvalidateWeakPtrs(); |
| mode_ = Mode::kNone; |
| } |
| |
| void FakeBiometric::OnFileCanWriteWithoutBlocking(int fd) { |
| NOTREACHED(); |
| } |
| |
| void FakeBiometric::OnFileCanReadWithoutBlocking(int fd) { |
| // We scan the stream for the magic bytes in case the previous input command |
| // was not the correct length or malformed for whatever reason. This must be |
| // done a single byte at a time because the input stream of bytes is totally |
| // unaligned. Reading the length of magic bytes at once might consume some |
| // garbage data and the start of the magic bytes, but that would fail to |
| // validate, and subsequent reads would never see that correct instance of |
| // magic bytes. |
| size_t magic_index = 0; |
| const uint8_t magic_start[] = {FAKE_BIOMETRIC_MAGIC_BYTES}; |
| while (magic_index < sizeof(magic_start)) { |
| uint8_t magic; |
| if (read(fd, &magic, 1) != 1) |
| return; |
| if (magic == magic_start[magic_index]) |
| magic_index++; |
| else |
| magic_index = 0; |
| } |
| |
| uint8_t cmd; |
| if (read(fd, &cmd, 1) != 1) |
| return; |
| switch (cmd) { |
| case 'A': { |
| uint8_t res_code; |
| if (read(fd, &res_code, 1) != 1) |
| return; |
| Biometric::ScanResult res = static_cast<Biometric::ScanResult>(res_code); |
| |
| uint8_t recognized_count; |
| if (read(fd, &recognized_count, 1) != 1) |
| return; |
| |
| std::vector<std::string> recognized_user_ids(recognized_count); |
| for (size_t i = 0; i < recognized_count; i++) { |
| uint8_t id_size; |
| if (read(fd, &id_size, 1) != 1) |
| return; |
| |
| std::string& user_id = recognized_user_ids[i]; |
| user_id.resize(id_size); |
| if (read(fd, &user_id.front(), id_size) != id_size) |
| return; |
| LOG(INFO) << "Recognized User " << user_id; |
| } |
| |
| if (!on_attempt_.is_null() && mode_ == Mode::kAuthentication) |
| on_attempt_.Run(res, recognized_user_ids); |
| return; |
| } |
| case 'S': { |
| uint8_t res_code; |
| if (read(fd, &res_code, 1) != 1) |
| return; |
| Biometric::ScanResult res = static_cast<Biometric::ScanResult>(res_code); |
| |
| uint8_t done; |
| if (read(fd, &done, 1) != 1) |
| return; |
| |
| LOG(INFO) << "Scan result " << static_cast<int>(res_code) << " done " |
| << static_cast<bool>(done); |
| |
| if (mode_ == Mode::kEnroll) { |
| if (done) { |
| enrollments_[next_enrollment_id_++] |
| = std::move(next_internal_enrollment_); |
| mode_ = Mode::kNone; |
| session_weak_factory_.InvalidateWeakPtrs(); |
| } |
| |
| if (!on_scan_.is_null()) |
| on_scan_.Run(res, done); |
| } |
| return; |
| } |
| case 'F': |
| LOG(INFO) << "Fake failure"; |
| if (!on_failure_.is_null()) |
| on_failure_.Run(); |
| } |
| } |
| } // namespace biod |