Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 400e8309 authored by Steven Liu's avatar Steven Liu
Browse files

Writes procedure data for HAL V2

Flag: com.android.bluetooth.flags.channel_sounding_25q2_apis
Bug: 367409858
Bug: 357090692

Test: m com.android.btservices

Change-Id: Iec9f44c686c54efab3414c0e42a203949b4a8c0f
parent ddef001f
Loading
Loading
Loading
Loading
+235 −0
Original line number Diff line number Diff line
@@ -23,6 +23,15 @@

namespace bluetooth {
namespace hal {
/**
 * See BLUETOOTH CORE SPECIFICATION Version 6.0 | Vol 4, Part E 7.7.65.44 ((LE CS Subevent Result)
 * for details.
 */
static constexpr uint8_t kInitiatorMeasuredOffsetBits = 15;
static constexpr uint16_t kUnavailableInitiatorMeasuredOffset = 0xC000;
static constexpr uint8_t kUnavailablePacketRssi = 0x7F;
static constexpr uint8_t kIQSampleBits = 12;

enum RangingHalVersion {
  V_UNKNOWN = 0,
  V_1 = 1,
@@ -47,6 +56,229 @@ struct ChannelSoundingRawData {
  std::vector<int16_t> tod_toa_reflectors_;
};

// TODO: move to a utility file and add UT.
template <int BITS>
static inline int16_t ConvertToSigned(uint16_t num) {
  unsigned msb_mask = 1 << (BITS - 1);  // setup a mask for most significant bit
  int16_t num_signed = num;
  if ((num_signed & msb_mask) != 0) {
    num_signed |= ~(msb_mask - 1);  // extend the MSB
  }
  return num_signed;
}

struct Mode0Data {
  Mode0Data(const hci::LeCsMode0InitatorData& le_cs_mode0_data)
      : packet_quality_(le_cs_mode0_data.packet_quality_),
        packet_rssi_(le_cs_mode0_data.packet_rssi_),
        packet_antenna_(le_cs_mode0_data.packet_antenna_),
        initiator_measured_offset(le_cs_mode0_data.measured_freq_offset_) {}

  Mode0Data(const hci::LeCsMode0ReflectorData& le_cs_mode0_data)
      : packet_quality_(le_cs_mode0_data.packet_quality_),
        packet_rssi_(le_cs_mode0_data.packet_rssi_),
        packet_antenna_(le_cs_mode0_data.packet_antenna_) {}

  uint8_t packet_quality_ = 0;
  uint8_t packet_rssi_ = kUnavailablePacketRssi;
  uint8_t packet_antenna_ = 0;
  uint16_t initiator_measured_offset = kUnavailableInitiatorMeasuredOffset;
};

struct Mode1Data {
  Mode1Data() {}
  Mode1Data(const hci::LeCsMode1InitatorData& le_cs_mode1_data)
      : packet_quality_(le_cs_mode1_data.packet_quality_),
        packet_nadm_(le_cs_mode1_data.packet_nadm_),
        packet_rssi_(le_cs_mode1_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode1_data.toa_tod_initiator_),
        packet_antenna_(le_cs_mode1_data.packet_antenna_) {}

  Mode1Data(const hci::LeCsMode1InitatorDataWithPacketPct& le_cs_mode1_data)
      : packet_quality_(le_cs_mode1_data.packet_quality_),
        packet_nadm_(le_cs_mode1_data.packet_nadm_),
        packet_rssi_(le_cs_mode1_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode1_data.toa_tod_initiator_),
        packet_antenna_(le_cs_mode1_data.packet_antenna_),
        i_packet_pct1_(le_cs_mode1_data.packet_pct1_.i_sample_),
        q_packet_pct1_(le_cs_mode1_data.packet_pct1_.q_sample_),
        i_packet_pct2_(le_cs_mode1_data.packet_pct2_.i_sample_),
        q_packet_pct2_(le_cs_mode1_data.packet_pct2_.q_sample_) {}

  Mode1Data(const hci::LeCsMode1ReflectorData& le_cs_mode1_data)
      : packet_quality_(le_cs_mode1_data.packet_quality_),
        packet_nadm_(le_cs_mode1_data.packet_nadm_),
        packet_rssi_(le_cs_mode1_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode1_data.tod_toa_reflector_),
        packet_antenna_(le_cs_mode1_data.packet_antenna_) {}

  Mode1Data(const hci::LeCsMode1ReflectorDataWithPacketPct& le_cs_mode1_data)
      : packet_quality_(le_cs_mode1_data.packet_quality_),
        packet_nadm_(le_cs_mode1_data.packet_nadm_),
        packet_rssi_(le_cs_mode1_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode1_data.tod_toa_reflector_),
        packet_antenna_(le_cs_mode1_data.packet_antenna_),
        i_packet_pct1_(le_cs_mode1_data.packet_pct1_.i_sample_),
        q_packet_pct1_(le_cs_mode1_data.packet_pct1_.q_sample_),
        i_packet_pct2_(le_cs_mode1_data.packet_pct2_.i_sample_),
        q_packet_pct2_(le_cs_mode1_data.packet_pct2_.q_sample_) {}

  Mode1Data(const hci::LeCsMode3InitatorData& le_cs_mode3_data)
      : packet_quality_(le_cs_mode3_data.packet_quality_),
        packet_nadm_(le_cs_mode3_data.packet_nadm_),
        packet_rssi_(le_cs_mode3_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode3_data.toa_tod_initiator_),
        packet_antenna_(le_cs_mode3_data.packet_antenna_) {}

  Mode1Data(const hci::LeCsMode3InitatorDataWithPacketPct& le_cs_mode3_data)
      : packet_quality_(le_cs_mode3_data.packet_quality_),
        packet_nadm_(le_cs_mode3_data.packet_nadm_),
        packet_rssi_(le_cs_mode3_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode3_data.toa_tod_initiator_),
        packet_antenna_(le_cs_mode3_data.packet_antenna_),
        i_packet_pct1_(le_cs_mode3_data.packet_pct1_.i_sample_),
        q_packet_pct1_(le_cs_mode3_data.packet_pct1_.q_sample_),
        i_packet_pct2_(le_cs_mode3_data.packet_pct2_.i_sample_),
        q_packet_pct2_(le_cs_mode3_data.packet_pct2_.q_sample_) {}

  Mode1Data(const hci::LeCsMode3ReflectorData& le_cs_mode3_data)
      : packet_quality_(le_cs_mode3_data.packet_quality_),
        packet_nadm_(le_cs_mode3_data.packet_nadm_),
        packet_rssi_(le_cs_mode3_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode3_data.tod_toa_reflector_),
        packet_antenna_(le_cs_mode3_data.packet_antenna_) {}

  Mode1Data(const hci::LeCsMode3ReflectorDataWithPacketPct& le_cs_mode3_data)
      : packet_quality_(le_cs_mode3_data.packet_quality_),
        packet_nadm_(le_cs_mode3_data.packet_nadm_),
        packet_rssi_(le_cs_mode3_data.packet_rssi_),
        rtt_toa_tod_data_(le_cs_mode3_data.tod_toa_reflector_),
        packet_antenna_(le_cs_mode3_data.packet_antenna_),
        i_packet_pct1_(le_cs_mode3_data.packet_pct1_.i_sample_),
        q_packet_pct1_(le_cs_mode3_data.packet_pct1_.q_sample_),
        i_packet_pct2_(le_cs_mode3_data.packet_pct2_.i_sample_),
        q_packet_pct2_(le_cs_mode3_data.packet_pct2_.q_sample_) {}

  uint8_t packet_quality_ = 0;
  hci::CsPacketNadm packet_nadm_ = hci::CsPacketNadm::UNKNOWN_NADM;
  uint8_t packet_rssi_ = kUnavailablePacketRssi;
  uint16_t rtt_toa_tod_data_ = 0;
  uint8_t packet_antenna_ = 0;
  std::optional<uint16_t> i_packet_pct1_ = std::nullopt;
  std::optional<uint16_t> q_packet_pct1_ = std::nullopt;
  std::optional<uint16_t> i_packet_pct2_ = std::nullopt;
  std::optional<uint16_t> q_packet_pct2_ = std::nullopt;
};

struct Mode2Data {
  Mode2Data() {}
  Mode2Data(const hci::LeCsMode2Data& le_cs_mode2_data)
      : antenna_permutation_index_(le_cs_mode2_data.antenna_permutation_index_) {
    std::copy(le_cs_mode2_data.tone_data_.begin(), le_cs_mode2_data.tone_data_.end(),
              std::back_inserter(tone_data_with_qualities_));
  }

  Mode2Data(const hci::LeCsMode3InitatorData& le_cs_mode3_data)
      : antenna_permutation_index_(le_cs_mode3_data.antenna_permutation_index_) {
    std::copy(le_cs_mode3_data.tone_data_.begin(), le_cs_mode3_data.tone_data_.end(),
              std::back_inserter(tone_data_with_qualities_));
  }

  Mode2Data(const hci::LeCsMode3InitatorDataWithPacketPct& le_cs_mode3_data)
      : antenna_permutation_index_(le_cs_mode3_data.antenna_permutation_index_) {
    std::copy(le_cs_mode3_data.tone_data_.begin(), le_cs_mode3_data.tone_data_.end(),
              std::back_inserter(tone_data_with_qualities_));
  }

  Mode2Data(const hci::LeCsMode3ReflectorData& le_cs_mode3_data)
      : antenna_permutation_index_(le_cs_mode3_data.antenna_permutation_index_) {
    std::copy(le_cs_mode3_data.tone_data_.begin(), le_cs_mode3_data.tone_data_.end(),
              std::back_inserter(tone_data_with_qualities_));
  }

  Mode2Data(const hci::LeCsMode3ReflectorDataWithPacketPct& le_cs_mode3_data)
      : antenna_permutation_index_(le_cs_mode3_data.antenna_permutation_index_) {
    std::copy(le_cs_mode3_data.tone_data_.begin(), le_cs_mode3_data.tone_data_.end(),
              std::back_inserter(tone_data_with_qualities_));
  }

  uint8_t antenna_permutation_index_ = 0;
  std::vector<hci::LeCsToneDataWithQuality> tone_data_with_qualities_;
};

struct Mode3Data {
  Mode3Data(const hci::LeCsMode3InitatorData& le_cs_mode3_data)
      : mode1_data_(le_cs_mode3_data), mode2_data_(le_cs_mode3_data) {}

  Mode3Data(const hci::LeCsMode3InitatorDataWithPacketPct& le_cs_mode3_data)
      : mode1_data_(le_cs_mode3_data), mode2_data_(le_cs_mode3_data) {}

  Mode3Data(const hci::LeCsMode3ReflectorData& le_cs_mode3_data)
      : mode1_data_(le_cs_mode3_data), mode2_data_(le_cs_mode3_data) {}

  Mode3Data(const hci::LeCsMode3ReflectorDataWithPacketPct& le_cs_mode3_data)
      : mode1_data_(le_cs_mode3_data), mode2_data_(le_cs_mode3_data) {}

  Mode1Data mode1_data_;
  Mode2Data mode2_data_;
};

struct StepSpecificData {
  uint8_t step_channel_;
  uint8_t mode_type_;
  // ModeSpecificData mode_specific_data_;
  std::variant<Mode0Data, Mode1Data, Mode2Data, Mode3Data> mode_specific_data_;
};

struct SubeventResult {
  /**
   * Starting ACL connection event counter for the results reported in the event
   */
  int start_acl_conn_event_counter_;
  /**
   * Frequency compensation value in units of 0.01 ppm (15-bit signed integer)
   * Unit: 0.01 ppm
   * 0xC000 - Frequency compensation value is not available, or the role is not initiator
   */
  uint16_t frequency_compensation_ = 0xC000;
  /**
   * Reference power level
   * Range: -127 to 20
   * Unit: dBm
   */
  uint8_t reference_power_level_;
  /**
   * 0x00 Ignored because phase measurement does not occur during the CS step
   * 0x01 to 0x04 Number of antenna paths used during the phase measurement stage of the CS step
   */
  uint8_t num_antenna_paths_;
  /**
   * Indicates the abort reason
   */
  hci::SubeventAbortReason subevent_abort_reason_;
  /**
   * The measured data for all steps
   */
  std::vector<StepSpecificData> step_data_;
  /**
   * Timestamp when all subevent data are received by the host; Not defined by the spec.
   * Using epoch time in nanos (e.g., 1697673127175).
   */
  long timestamp_nanos_;
};

struct ProcedureDataV2 {
  // for HAL v2
  std::vector<std::shared_ptr<hal::SubeventResult>> local_subevent_data_;
  std::vector<std::shared_ptr<hal::SubeventResult>> remote_subevent_data_;
  hci::ProcedureAbortReason local_procedure_abort_reason_;
  hci::ProcedureAbortReason remote_procedure_abort_reason_;
  uint8_t local_selected_tx_power_;
  uint8_t remote_selected_tx_power_;
  // TODO(b/378942784): assign the sequence
  int procedure_sequence_;
};

struct RangingResult {
  double result_meters_;
  // A normalized value from 0 (low confidence) to 100 (high confidence) representing the confidence
@@ -86,6 +318,9 @@ public:
  virtual void UpdateProcedureEnableConfig(
          uint16_t connection_handle,
          const hci::LeCsProcedureEnableCompleteView& leCsProcedureEnableCompleteView) = 0;
  virtual void WriteProcedureData(uint16_t connection_handle, hci::CsRole local_cs_role,
                                  const ProcedureDataV2& procedure_data,
                                  uint16_t procedure_counter) = 0;
};

}  // namespace hal
+173 −0
Original line number Diff line number Diff line
@@ -51,6 +51,21 @@ using aidl::android::hardware::bluetooth::ranging::SubModeType;
using aidl::android::hardware::bluetooth::ranging::VendorSpecificData;
// using aidl::android::hardware::bluetooth::ranging::

using aidl::android::hardware::bluetooth::ranging::ChannelSoundingProcedureData;
using aidl::android::hardware::bluetooth::ranging::ModeData;
using aidl::android::hardware::bluetooth::ranging::ModeOneData;
using aidl::android::hardware::bluetooth::ranging::ModeThreeData;
using aidl::android::hardware::bluetooth::ranging::ModeTwoData;
using aidl::android::hardware::bluetooth::ranging::ModeType;
using aidl::android::hardware::bluetooth::ranging::ModeZeroData;
using aidl::android::hardware::bluetooth::ranging::Nadm;
using aidl::android::hardware::bluetooth::ranging::PctIQSample;
using aidl::android::hardware::bluetooth::ranging::ProcedureAbortReason;
using aidl::android::hardware::bluetooth::ranging::RttToaTodData;
using aidl::android::hardware::bluetooth::ranging::StepData;
using aidl::android::hardware::bluetooth::ranging::SubeventAbortReason;
using aidl::android::hardware::bluetooth::ranging::SubeventResultData;

namespace bluetooth {
namespace hal {

@@ -322,6 +337,164 @@ public:
    it->second->GetSession()->updateProcedureEnableConfig(pConfig);
  }

  void WriteProcedureData(const uint16_t connection_handle, hci::CsRole local_cs_role,
                          const ProcedureDataV2& procedure_data,
                          uint16_t procedure_counter) override {
    auto session_it = session_trackers_.find(connection_handle);
    if (session_it == session_trackers_.end()) {
      log::error("Can't find session for connection_handle:0x{:04x}", connection_handle);
      return;
    } else if (session_it->second->GetSession() == nullptr) {
      log::error("Session not opened");
      return;
    }
    ChannelSoundingProcedureData channel_sounding_procedure_data;
    channel_sounding_procedure_data.procedureCounter = procedure_counter;
    channel_sounding_procedure_data.procedureSequence = procedure_data.procedure_sequence_;

    if (local_cs_role == hci::CsRole::INITIATOR) {
      channel_sounding_procedure_data.initiatorSelectedTxPower =
              static_cast<int8_t>(procedure_data.local_selected_tx_power_);
      channel_sounding_procedure_data.reflectorSelectedTxPower =
              static_cast<int8_t>(procedure_data.remote_selected_tx_power_);
      channel_sounding_procedure_data.initiatorProcedureAbortReason =
              static_cast<ProcedureAbortReason>(procedure_data.local_procedure_abort_reason_);
      channel_sounding_procedure_data.reflectorProcedureAbortReason =
              static_cast<ProcedureAbortReason>(procedure_data.remote_procedure_abort_reason_);
      channel_sounding_procedure_data.initiatorSubeventResultData =
              get_subevent_result_data(procedure_data.local_subevent_data_, hci::CsRole::INITIATOR);
      channel_sounding_procedure_data.reflectorSubeventResultData = get_subevent_result_data(
              procedure_data.remote_subevent_data_, hci::CsRole::REFLECTOR);
    } else {
      channel_sounding_procedure_data.initiatorSelectedTxPower =
              static_cast<int8_t>(procedure_data.remote_selected_tx_power_);
      channel_sounding_procedure_data.reflectorSelectedTxPower =
              static_cast<int8_t>(procedure_data.local_selected_tx_power_);
      channel_sounding_procedure_data.initiatorProcedureAbortReason =
              static_cast<ProcedureAbortReason>(procedure_data.remote_procedure_abort_reason_);
      channel_sounding_procedure_data.reflectorProcedureAbortReason =
              static_cast<ProcedureAbortReason>(procedure_data.local_procedure_abort_reason_);
      channel_sounding_procedure_data.initiatorSubeventResultData = get_subevent_result_data(
              procedure_data.remote_subevent_data_, hci::CsRole::INITIATOR);
      channel_sounding_procedure_data.reflectorSubeventResultData =
              get_subevent_result_data(procedure_data.local_subevent_data_, hci::CsRole::REFLECTOR);

      session_it->second->GetSession()->writeProcedureData(channel_sounding_procedure_data);
    }
  }

  static std::vector<SubeventResultData> get_subevent_result_data(
          const std::vector<std::shared_ptr<SubeventResult>>& subevent_results,
          hci::CsRole cs_role) {
    std::vector<SubeventResultData> hal_subevents;
    for (auto subevent_result : subevent_results) {
      SubeventResultData aidl_subevent_result{
              .startAclConnEventCounter = subevent_result->start_acl_conn_event_counter_,
              .frequencyCompensation = ConvertToSigned<kInitiatorMeasuredOffsetBits>(
                      subevent_result->frequency_compensation_),
              .referencePowerLevelDbm =
                      static_cast<int8_t>(subevent_result->reference_power_level_),
              .numAntennaPaths = static_cast<int8_t>(subevent_result->num_antenna_paths_),
              .subeventAbortReason =
                      static_cast<SubeventAbortReason>(subevent_result->subevent_abort_reason_),
              .stepData = get_group_step_data(subevent_result->step_data_, cs_role),
              .timestampNanos = subevent_result->timestamp_nanos_,
      };
      hal_subevents.push_back(aidl_subevent_result);
    }
    return hal_subevents;
  }

  static std::vector<StepData> get_group_step_data(
          const std::vector<StepSpecificData>& step_specific_data_list, hci::CsRole cs_role) {
    std::vector<StepData> group_step_data;
    for (auto step_specific_data : step_specific_data_list) {
      StepData step_data{
              .stepChannel = static_cast<int8_t>(step_specific_data.step_channel_),
              .stepMode = static_cast<ModeType>(step_specific_data.mode_type_),
      };
      get_step_mode_data(step_specific_data.mode_specific_data_, step_data.stepModeData, cs_role);
      group_step_data.push_back(step_data);
    }
    return group_step_data;
  }

  static void get_step_mode_data(
          std::variant<Mode0Data, Mode1Data, Mode2Data, Mode3Data> mode_specific_data,
          ModeData& mode_data, hci::CsRole cs_role) {
    if (std::holds_alternative<Mode0Data>(mode_specific_data)) {
      auto mode_0_data = std::get<Mode0Data>(mode_specific_data);
      ModeZeroData mode_zero_data{
              .packetQuality = static_cast<int8_t>(mode_0_data.packet_quality_),
              .packetRssiDbm = static_cast<int8_t>(mode_0_data.packet_rssi_),
              .packetAntenna = static_cast<int8_t>(mode_0_data.packet_antenna_),
      };
      mode_data = mode_zero_data;
      return;
    }
    if (std::holds_alternative<Mode1Data>(mode_specific_data)) {
      auto mode_1_data = std::get<Mode1Data>(mode_specific_data);
      mode_data = convert_mode_1_data(mode_1_data, cs_role);
      return;
    }
    if (std::holds_alternative<Mode2Data>(mode_specific_data)) {
      auto mode_2_data = std::get<Mode2Data>(mode_specific_data);
      mode_data = convert_mode_2_data(mode_2_data);
      return;
    }
    if (std::holds_alternative<Mode3Data>(mode_specific_data)) {
      auto mode_3_data = std::get<Mode3Data>(mode_specific_data);
      ModeThreeData mode_three_data{
              .modeOneData = convert_mode_1_data(mode_3_data.mode1_data_, cs_role),
              .modeTwoData = convert_mode_2_data(mode_3_data.mode2_data_),
      };
      mode_data = mode_three_data;
      return;
    }
  }

  static ModeOneData convert_mode_1_data(const Mode1Data& mode_1_data, hci::CsRole cs_role) {
    ModeOneData mode_one_data{
            .packetQuality = static_cast<int8_t>(mode_1_data.packet_quality_),
            .packetNadm = static_cast<Nadm>(mode_1_data.packet_nadm_),
            .packetRssiDbm = static_cast<int8_t>(mode_1_data.packet_rssi_),
            .packetAntenna = static_cast<int8_t>(mode_1_data.packet_antenna_),
    };
    if (cs_role == hci::CsRole::INITIATOR) {
      mode_one_data.rttToaTodData = RttToaTodData::make<RttToaTodData::Tag::toaTodInitiator>(
              static_cast<int16_t>(mode_1_data.rtt_toa_tod_data_));
    } else {
      mode_one_data.rttToaTodData = RttToaTodData::make<RttToaTodData::Tag::todToaReflector>(
              static_cast<int16_t>(mode_1_data.rtt_toa_tod_data_));
    }
    // TODO(b/378942784): once get 32 bits from controller, and check the unavailable data.
    if (mode_1_data.i_packet_pct1_.has_value()) {
      mode_one_data.packetPct1.emplace(
              ConvertToSigned<kIQSampleBits>(mode_1_data.i_packet_pct1_.value()),
              ConvertToSigned<kIQSampleBits>(mode_1_data.q_packet_pct1_.value()));
    }
    if (mode_1_data.i_packet_pct2_.has_value()) {
      mode_one_data.packetPct2.emplace(
              ConvertToSigned<kIQSampleBits>(mode_1_data.i_packet_pct2_.value()),
              ConvertToSigned<kIQSampleBits>(mode_1_data.q_packet_pct2_.value()));
    }
    return mode_one_data;
  }

  static ModeTwoData convert_mode_2_data(const Mode2Data& mode_2_data) {
    ModeTwoData mode_two_data{
            .antennaPermutationIndex = static_cast<int8_t>(mode_2_data.antenna_permutation_index_),
    };
    for (const auto& tone_data_with_quality : mode_2_data.tone_data_with_qualities_) {
      mode_two_data.toneQualityIndicators.emplace_back(
              tone_data_with_quality.tone_quality_indicator_);
      mode_two_data.tonePctIQSamples.emplace_back(
              ConvertToSigned<kIQSampleBits>(tone_data_with_quality.i_sample_),
              ConvertToSigned<kIQSampleBits>(tone_data_with_quality.q_sample_));
    }
    return mode_two_data;
  }

  void CopyVendorSpecificData(const std::vector<hal::VendorSpecificCharacteristic>& source,
                              std::optional<std::vector<std::optional<VendorSpecificData>>>& dist) {
    dist = std::make_optional<std::vector<std::optional<VendorSpecificData>>>();
+4 −0
Original line number Diff line number Diff line
@@ -54,6 +54,10 @@ public:
          const hci::LeCsProcedureEnableCompleteView& /* leCsProcedureEnableCompleteView */)
          override {}

  void WriteProcedureData(uint16_t /* connection_handle */, hci::CsRole /* local_cs_role */,
                          const ProcedureDataV2& /* procedure_data */,
                          uint16_t /* procedure_counter */) {}

protected:
  void ListDependencies(ModuleList* /*list*/) const {}

+214 −47

File changed.

Preview size limit exceeded, changes collapsed.