// Copyright 2017 The Chromium 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 DEVICE_FIDO_FIDO_BLE_FRAMES_H_
#define DEVICE_FIDO_FIDO_BLE_FRAMES_H_

#include <stdint.h>

#include <utility>
#include <vector>

#include "base/component_export.h"
#include "base/containers/queue.h"
#include "base/containers/span.h"
#include "base/macros.h"
#include "device/fido/fido_constants.h"

namespace device {

class FidoBleFrameInitializationFragment;
class FidoBleFrameContinuationFragment;

// Encapsulates a frame, i.e., a single request to or response from a FIDO
// compliant authenticator, designed to be transported via BLE. The frame is
// further split into fragments (see FidoBleFrameFragment class).
//
// The specification of what constitues a frame can be found here:
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing
//
// TODO(crbug/763303): Consider refactoring U2fMessage to support BLE frames.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrame {
 public:
  // The values which can be carried in the |data| section of a KEEPALIVE
  // message sent from an authenticator.
  enum class KeepaliveCode : uint8_t {
    // The request is still being processed. The authenticator will be sending
    // this message every |kKeepAliveMillis| milliseconds until completion.
    PROCESSING = 0x01,
    // The authenticator is waiting for the Test of User Presence to complete.
    TUP_NEEDED = 0x02,
  };

  // The types of errors an authenticator can return to the client. Carried in
  // the |data| section of an ERROR command.
  enum class ErrorCode : uint8_t {
    INVALID_CMD = 0x01,  // The command in the request is unknown/invalid.
    INVALID_PAR = 0x02,  // The parameters of the command are invalid/missing.
    INVALID_LEN = 0x03,  // The length of the request is invalid.
    INVALID_SEQ = 0x04,  // The sequence number is invalid.
    REQ_TIMEOUT = 0x05,  // The request timed out.
    NA_1 = 0x06,         // Value reserved (HID).
    NA_2 = 0x0A,         // Value reserved (HID).
    NA_3 = 0x0B,         // Value reserved (HID).
    ENCRYPTION_FAILED = 0x0C,  // Encryption failed for the request.
    OTHER = 0x7F,              // Other, unspecified error.
  };

  FidoBleFrame();
  FidoBleFrame(FidoBleDeviceCommand command, std::vector<uint8_t> data);

  FidoBleFrame(FidoBleFrame&&);
  FidoBleFrame& operator=(FidoBleFrame&&);

  ~FidoBleFrame();

  FidoBleDeviceCommand command() const { return command_; }

  bool IsValid() const;
  KeepaliveCode GetKeepaliveCode() const;
  ErrorCode GetErrorCode() const;

  const std::vector<uint8_t>& data() const { return data_; }
  std::vector<uint8_t>& data() { return data_; }

  // Splits the frame into fragments suitable for sending over BLE. Returns the
  // first fragment via |initial_fragment|, and pushes the remaining ones back
  // to the |other_fragments| vector.
  //
  // The |max_fragment_size| parameter ought to be at least 3. The resulting
  // fragments' binary sizes will not exceed this value.
  std::pair<FidoBleFrameInitializationFragment,
            base::queue<FidoBleFrameContinuationFragment>>
  ToFragments(size_t max_fragment_size) const;

 private:
  FidoBleDeviceCommand command_ = FidoBleDeviceCommand::kMsg;
  std::vector<uint8_t> data_;

  DISALLOW_COPY_AND_ASSIGN(FidoBleFrame);
};

// A single frame sent over BLE may be split over multiple writes and
// notifications because the technology was not designed for large messages.
// This class represents a single fragment. Not to be used directly.
//
// A frame is divided into an initialization fragment and zero, one or more
// continuation fragments. See the below section of the spec for the details:
// https://fidoalliance.org/specs/fido-u2f-v1.2-ps-20170411/fido-u2f-bt-protocol-v1.2-ps-20170411.html#h2_framing-fragmentation
//
// Note: This class and its subclasses don't own the |data|.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameFragment {
 public:
  base::span<const uint8_t> fragment() const { return fragment_; }
  virtual size_t Serialize(std::vector<uint8_t>* buffer) const = 0;

 protected:
  FidoBleFrameFragment();
  explicit FidoBleFrameFragment(base::span<const uint8_t> fragment);
  FidoBleFrameFragment(const FidoBleFrameFragment& frame);
  virtual ~FidoBleFrameFragment();

 private:
  base::span<const uint8_t> fragment_;
};

// An initialization fragment of a frame.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameInitializationFragment
    : public FidoBleFrameFragment {
 public:
  static bool Parse(base::span<const uint8_t> data,
                    FidoBleFrameInitializationFragment* fragment);

  FidoBleFrameInitializationFragment() = default;
  FidoBleFrameInitializationFragment(FidoBleDeviceCommand command,
                                     uint16_t data_length,
                                     base::span<const uint8_t> fragment)
      : FidoBleFrameFragment(fragment),
        command_(command),
        data_length_(data_length) {}

  FidoBleDeviceCommand command() const { return command_; }
  uint16_t data_length() const { return data_length_; }

  size_t Serialize(std::vector<uint8_t>* buffer) const override;

 private:
  FidoBleDeviceCommand command_ = FidoBleDeviceCommand::kMsg;
  uint16_t data_length_ = 0;
};

// A continuation fragment of a frame.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameContinuationFragment
    : public FidoBleFrameFragment {
 public:
  static bool Parse(base::span<const uint8_t> data,
                    FidoBleFrameContinuationFragment* fragment);

  FidoBleFrameContinuationFragment() = default;
  FidoBleFrameContinuationFragment(base::span<const uint8_t> fragment,
                                   uint8_t sequence)
      : FidoBleFrameFragment(fragment), sequence_(sequence) {}

  uint8_t sequence() const { return sequence_; }

  size_t Serialize(std::vector<uint8_t>* buffer) const override;

 private:
  uint8_t sequence_ = 0;
};

// The helper used to construct a FidoBleFrame from a sequence of its fragments.
class COMPONENT_EXPORT(DEVICE_FIDO) FidoBleFrameAssembler {
 public:
  explicit FidoBleFrameAssembler(
      const FidoBleFrameInitializationFragment& fragment);
  ~FidoBleFrameAssembler();

  bool IsDone() const;

  bool AddFragment(const FidoBleFrameContinuationFragment& fragment);
  FidoBleFrame* GetFrame();

 private:
  uint16_t data_length_ = 0;
  uint8_t sequence_number_ = 0;
  FidoBleFrame frame_;

  DISALLOW_COPY_AND_ASSIGN(FidoBleFrameAssembler);
};

}  // namespace device

#endif  // DEVICE_FIDO_FIDO_BLE_FRAMES_H_
