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

Commit 8145d602 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

LeAudio: Improve LTV map structure

Add the compare operators, a way to get two maps intersection
and a more convenient raw data parsing helper.

This is not used with the current set of features but will be
needed for the upcoming multi-codec support.

Bug: 295972694
Test: atest --host bluetooth_le_audio_test bluetooth_le_audio_client_test bluetooth_test_broadcaster bluetooth_test_broadcaster_state_machine bluetooth_le_audio_codec_manager_test
Flag: EXEMPT; not used yet (will be used by the upcoming multicodec feature), verified with unit tests
Change-Id: Ia65e480497121e624ff87d471b65d325e7010528
parent 07805ac1
Loading
Loading
Loading
Loading
+32 −5
Original line number Diff line number Diff line
@@ -503,12 +503,21 @@ void LeAudioLtvMap::Append(const LeAudioLtvMap& other) {
  for (auto& el : other.values) {
    values[el.first] = el.second;
  }

  invalidate();
}

LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len,
                                   bool& success) {
  LeAudioLtvMap ltv_map;
  success = ltv_map.Parse(p_value, len);
  if (!success) {
    LOG(ERROR) << __func__ << "Error parsing LTV map";
  }
  return ltv_map;
}

bool LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len) {
  if (len > 0) {
    const auto p_value_end = p_value + len;

@@ -522,8 +531,8 @@ LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len,
      if (p_value_end < (p_value + ltv_len)) {
        LOG(ERROR) << __func__
                   << " Invalid ltv_len: " << static_cast<int>(ltv_len);
        success = false;
        return LeAudioLtvMap();
        invalidate();
        return false;
      }

      uint8_t ltv_type;
@@ -534,12 +543,12 @@ LeAudioLtvMap LeAudioLtvMap::Parse(const uint8_t* p_value, uint8_t len,
      p_value += ltv_len;

      std::vector<uint8_t> ltv_value(p_temp, p_value);
      ltv_map.values.emplace(ltv_type, std::move(ltv_value));
      values.emplace(ltv_type, std::move(ltv_value));
    }
  }
  invalidate();

  success = true;
  return ltv_map;
  return true;
}

size_t LeAudioLtvMap::RawPacketSize() const {
@@ -595,6 +604,24 @@ LeAudioLtvMap::GetAsCoreCodecCapabilities() const {
  return *core_capabilities;
}

void LeAudioLtvMap::RemoveAllTypes(const LeAudioLtvMap& other) {
  for (auto const& [key, _] : other.values) {
    Remove(key);
  }
}

LeAudioLtvMap LeAudioLtvMap::GetIntersection(const LeAudioLtvMap& other) const {
  LeAudioLtvMap result;
  for (auto const& [key, value] : values) {
    auto entry = other.Find(key);
    if (entry->size() != value.size()) continue;
    if (memcmp(entry->data(), value.data(), value.size()) == 0) {
      result.Add(key, value);
    }
  }
  return result;
}

}  // namespace types

void AppendMetadataLtvEntryForCcidList(std::vector<uint8_t>& metadata,
+80 −13
Original line number Diff line number Diff line
@@ -643,23 +643,56 @@ struct LeAudioCoreCodecCapabilities {

class LeAudioLtvMap {
 public:
  LeAudioLtvMap() {}
  LeAudioLtvMap(std::map<uint8_t, std::vector<uint8_t>> values)
      : values(std::move(values)), core_config(std::nullopt) {}
      : values(values),
        value_hash(0),
        core_config(std::nullopt),
        core_capabilities(std::nullopt) {}
  LeAudioLtvMap() = default;
  ~LeAudioLtvMap() = default;

  bool operator==(const LeAudioLtvMap& other) const {
    return GetHash() == other.GetHash();
  }

  bool operator!=(const LeAudioLtvMap& other) const {
    return GetHash() != other.GetHash();
  }

  std::optional<std::vector<uint8_t>> Find(uint8_t type) const;
  const auto& At(uint8_t type) const { return values.at(type); }

  LeAudioLtvMap& Add(uint8_t type, std::vector<uint8_t> value) {
    values.insert_or_assign(type, std::move(value));
    core_config = std::nullopt;
    invalidate();
    return *this;
  }

  LeAudioLtvMap& Add(uint8_t type, const std::string& value) {
    std::vector<uint8_t> v(value.size());
    auto ptr = v.data();
    ARRAY_TO_STREAM(ptr, value.c_str(), (int)value.size());
    values.insert_or_assign(type, v);
    invalidate();
    return *this;
  }

  LeAudioLtvMap& Add(uint8_t type, bool value) {
    std::vector<uint8_t> v(1);
    auto ptr = v.data();
    UINT8_TO_STREAM(ptr, value ? 0x01 : 0x00);
    values.insert_or_assign(type, v);
    invalidate();
    return *this;
  }

  LeAudioLtvMap& Add(uint8_t type, uint8_t value) {
    std::vector<uint8_t> v(sizeof(value));
    auto ptr = v.data();

    UINT8_TO_STREAM(ptr, value);
    values.insert_or_assign(type, v);
    core_config = std::nullopt;
    invalidate();
    return *this;
  }
  LeAudioLtvMap& Add(uint8_t type, uint16_t value) {
@@ -668,7 +701,7 @@ class LeAudioLtvMap {

    UINT16_TO_STREAM(ptr, value);
    values.insert_or_assign(type, std::move(v));
    core_config = std::nullopt;
    invalidate();
    return *this;
  }
  LeAudioLtvMap& Add(uint8_t type, uint32_t value) {
@@ -677,12 +710,19 @@ class LeAudioLtvMap {

    UINT32_TO_STREAM(ptr, value);
    values.insert_or_assign(type, std::move(v));
    core_config = std::nullopt;
    invalidate();
    return *this;
  }
  void Remove(uint8_t type) { values.erase(type); }
  void Remove(uint8_t type) {
    values.erase(type);
    invalidate();
  }
  void RemoveAllTypes(const LeAudioLtvMap& other);
  bool IsEmpty() const { return values.empty(); }
  void Clear() { values.clear(); }
  void Clear() {
    invalidate();
    values.clear();
  }
  size_t Size() const { return values.size(); }
  const std::map<uint8_t, std::vector<uint8_t>>& Values() const {
    return values;
@@ -690,6 +730,7 @@ class LeAudioLtvMap {

  const struct LeAudioCoreCodecConfig& GetAsCoreCodecConfig() const;
  const struct LeAudioCoreCodecCapabilities& GetAsCoreCodecCapabilities() const;
  LeAudioLtvMap GetIntersection(const LeAudioLtvMap& other) const;

  std::string ToString(
      const std::string& indent_string,
@@ -698,10 +739,22 @@ class LeAudioLtvMap {
  uint8_t* RawPacket(uint8_t* p_buf) const;
  std::vector<uint8_t> RawPacket() const;
  static LeAudioLtvMap Parse(const uint8_t* value, uint8_t len, bool& success);
  bool Parse(const uint8_t* value, uint8_t len);
  void Append(const LeAudioLtvMap& other);
  size_t GetHash() const {
    if (value_hash == 0) RecalculateValueHash();
    return value_hash;
  }

 private:
  static LeAudioCoreCodecConfig LtvMapToCoreCodecConfig(LeAudioLtvMap ltvs) {
  void invalidate() {
    core_config = std::nullopt;
    core_capabilities = std::nullopt;
    value_hash = 0;
  }

  static LeAudioCoreCodecConfig LtvMapToCoreCodecConfig(
      const LeAudioLtvMap& ltvs) {
    LeAudioCoreCodecConfig core;

    auto vec_opt = ltvs.Find(codec_spec_conf::kLeAudioLtvTypeSamplingFreq);
@@ -750,7 +803,7 @@ class LeAudioLtvMap {
  }

  static LeAudioCoreCodecCapabilities LtvMapToCoreCodecCapabilities(
      LeAudioLtvMap pacs) {
      const LeAudioLtvMap& pacs) {
    LeAudioCoreCodecCapabilities core;

    auto pac =
@@ -819,10 +872,24 @@ class LeAudioLtvMap {
    return core;
  }

  std::map<uint8_t, std::vector<uint8_t>> values;
  void RecalculateValueHash() const {
    if (IsEmpty()) {
      value_hash = 0;
      return;
    }

    auto value_vec = RawPacket();
    value_hash = std::hash<std::string_view>{}(
        {reinterpret_cast<const char*>(value_vec.data()), value_vec.size()});
  }

  std::map<uint8_t, std::vector<uint8_t>> values = {};
  mutable size_t value_hash = 0;
  // Lazy-constructed views of the LTV data
  mutable std::optional<struct LeAudioCoreCodecConfig> core_config;
  mutable std::optional<struct LeAudioCoreCodecCapabilities> core_capabilities;
  mutable std::optional<struct LeAudioCoreCodecConfig> core_config =
      std::nullopt;
  mutable std::optional<struct LeAudioCoreCodecCapabilities> core_capabilities =
      std::nullopt;
};

struct LeAudioCodecId {
+243 −0
Original line number Diff line number Diff line
@@ -58,7 +58,9 @@ TEST(LeAudioLtvMapTest, test_serialization) {
  bool success;
  LeAudioLtvMap ltv_map =
      LeAudioLtvMap::Parse(ltv_test_vec.data(), ltv_test_vec.size(), success);
  auto hash_one = ltv_map.GetHash();
  ASSERT_TRUE(success);
  ASSERT_NE(hash_one, 0lu);
  ASSERT_FALSE(ltv_map.IsEmpty());
  ASSERT_EQ((size_t)3, ltv_map.Size());

@@ -67,11 +69,17 @@ TEST(LeAudioLtvMapTest, test_serialization) {

  LeAudioLtvMap ltv_map2 =
      LeAudioLtvMap::Parse(ltv_test_vec2.data(), ltv_test_vec2.size(), success);
  auto hash_two = ltv_map2.GetHash();
  ASSERT_TRUE(success);
  ASSERT_NE(hash_two, 0lu);
  ASSERT_FALSE(ltv_map2.IsEmpty());
  ASSERT_EQ((size_t)2, ltv_map2.Size());
  ASSERT_NE(hash_one, hash_two);

  ltv_map.Append(ltv_map2);
  ASSERT_NE(ltv_map.GetHash(), 0lu);
  ASSERT_NE(ltv_map.GetHash(), hash_one);
  ASSERT_NE(ltv_map.GetHash(), hash_two);
  ASSERT_EQ((size_t)4, ltv_map.Size());

  ASSERT_TRUE(ltv_map.Find(0x01));
@@ -372,5 +380,240 @@ TEST(LeAudioLtvMapTest, test_capabilities_valid) {
  ASSERT_FALSE(caps.IsCodecFramesPerSduSupported(3));
}

TEST(LeAudioLtvMapTest, test_adding_types) {
  LeAudioLtvMap ltv_map;
  ltv_map.Add(1, (uint8_t)127);
  ltv_map.Add(2, (uint16_t)32767);
  ltv_map.Add(3, (uint32_t)65535);
  ltv_map.Add(4, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});
  ltv_map.Add(5, std::string("sample text"));
  ltv_map.Add(6, true);

  ASSERT_EQ(6lu, ltv_map.Size());

  uint8_t u8;
  auto pp = ltv_map.At(1).data();
  STREAM_TO_UINT8(u8, pp);
  ASSERT_EQ((uint8_t)127, u8);

  uint16_t u16;
  pp = ltv_map.At(2).data();
  STREAM_TO_UINT16(u16, pp);
  ASSERT_EQ((uint16_t)32767, u16);

  uint32_t u32;
  pp = ltv_map.At(3).data();
  STREAM_TO_UINT32(u32, pp);
  ASSERT_EQ((uint32_t)65535, u32);

  ASSERT_EQ((std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9}), ltv_map.At(4));

  ASSERT_EQ(std::string("sample text"),
            std::string(ltv_map.At(5).begin(), ltv_map.At(5).end()));

  ASSERT_EQ(true, (bool)ltv_map.At(6).data()[0]);
}

TEST(LeAudioLtvMapTest, test_hash_sanity) {
  LeAudioLtvMap ltv_map;
  ASSERT_EQ(ltv_map.GetHash(), 0lu);

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ltv_map.Add(1, (uint16_t)32767);
  ltv_map.Add(2, (uint32_t)65535);
  ltv_map.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});

  ASSERT_NE(ltv_map.GetHash(), 0lu);
  ASSERT_NE(ltv_map.GetHash(), hash);
  ASSERT_EQ(ltv_map, ltv_map);

  // Compare hashes of equal LTV maps, filled in a different order
  LeAudioLtvMap ltv_map_two;
  ltv_map_two.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});
  ltv_map_two.Add(0, (uint8_t)127);
  ltv_map_two.Add(2, (uint32_t)65535);
  ltv_map_two.Add(1, (uint16_t)32767);
  ASSERT_EQ(ltv_map, ltv_map_two);
}

TEST(LeAudioLtvMapTest, test_value_hash_sanity) {
  LeAudioLtvMap ltv_map;

  ltv_map.Add(1, (uint16_t)32767);
  auto hash = ltv_map.GetHash();

  // Same value but type size is different
  hash = ltv_map.GetHash();
  ltv_map.Add(1, (uint32_t)32767);
  ASSERT_NE(ltv_map.GetHash(), hash);
}

TEST(LeAudioLtvMapTest, test_type_change_same_value) {
  LeAudioLtvMap ltv_map_one;
  ltv_map_one.Add(1, (uint16_t)32767);

  LeAudioLtvMap ltv_map_two;
  // The same value but different type
  ltv_map_two.Add(3, (uint16_t)32767);

  ASSERT_NE(ltv_map_one.GetHash(), ltv_map_two.GetHash());
}

TEST(LeAudioLtvMapTest, test_add_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(1, (uint16_t)32767);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(2, (uint32_t)65535);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});
  ASSERT_NE(ltv_map.GetHash(), hash);
}

TEST(LeAudioLtvMapTest, test_update_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint16_t)32767);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint32_t)65535);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(0, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});
  ASSERT_NE(ltv_map.GetHash(), hash);
}

TEST(LeAudioLtvMapTest, test_update_same_not_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ASSERT_EQ(ltv_map.GetHash(), hash);
}

TEST(LeAudioLtvMapTest, test_remove_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ltv_map.Add(1, (uint16_t)32767);
  ltv_map.Add(2, (uint32_t)65535);
  ltv_map.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});

  hash = ltv_map.GetHash();
  ltv_map.Remove(0);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Remove(1);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Remove(2);
  ASSERT_NE(ltv_map.GetHash(), hash);

  hash = ltv_map.GetHash();
  ltv_map.Remove(3);
  ASSERT_NE(ltv_map.GetHash(), hash);
}

TEST(LeAudioLtvMapTest, test_clear_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ltv_map.Add(1, (uint16_t)32767);
  ltv_map.Add(2, (uint32_t)65535);
  ltv_map.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});

  hash = ltv_map.GetHash();
  ltv_map.Clear();
  ASSERT_NE(ltv_map.GetHash(), hash);

  // 2nd clear should not change it
  hash = ltv_map.GetHash();
  ltv_map.Clear();
  ASSERT_EQ(ltv_map.GetHash(), hash);

  // Check if empty maps have equal hash
  LeAudioLtvMap empty_ltv_map;
  ASSERT_EQ(empty_ltv_map, ltv_map);
}

TEST(LeAudioLtvMapTest, test_remove_all_changing_hash) {
  LeAudioLtvMap ltv_map;

  auto hash = ltv_map.GetHash();
  ltv_map.Add(0, (uint8_t)127);
  ltv_map.Add(1, (uint16_t)32767);
  ltv_map.Add(2, (uint32_t)65535);
  ltv_map.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});

  LeAudioLtvMap ltv_map_1st_half;
  ltv_map_1st_half.Add(1, (uint16_t)32767);
  ltv_map_1st_half.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});

  LeAudioLtvMap ltv_map_2nd_half;
  ltv_map_2nd_half.Add(0, (uint8_t)127);
  ltv_map_2nd_half.Add(2, (uint32_t)65535);

  ASSERT_NE(ltv_map_1st_half, ltv_map_2nd_half);
  ASSERT_NE(ltv_map, ltv_map_2nd_half);

  hash = ltv_map.GetHash();
  ltv_map.RemoveAllTypes(ltv_map_1st_half);
  ASSERT_NE(hash, ltv_map.GetHash());

  hash = ltv_map.GetHash();
  ltv_map.RemoveAllTypes(ltv_map_2nd_half);
  ASSERT_NE(hash, ltv_map.GetHash());

  // Check if empty maps have equal hash
  LeAudioLtvMap empty_ltv_map;
  ASSERT_EQ(empty_ltv_map, ltv_map);
}

TEST(LeAudioLtvMapTest, test_intersection) {
  LeAudioLtvMap ltv_map_one;
  ltv_map_one.Add(1, (uint16_t)32767);
  ltv_map_one.Add(3, std::vector<uint8_t>{1, 2, 3, 4, 5, 6, 7, 8, 9});
  ltv_map_one.Add(2, (uint32_t)65535);

  LeAudioLtvMap ltv_map_two;
  ltv_map_two.Add(0, (uint8_t)127);
  // Not the type is the same but value differs
  ltv_map_two.Add(1, (uint16_t)32766);
  ltv_map_two.Add(2, (uint32_t)65535);

  LeAudioLtvMap ltv_map_common;
  ltv_map_common.Add(2, (uint32_t)65535);
  ASSERT_NE(ltv_map_common.GetHash(), 0lu);

  ASSERT_EQ(ltv_map_one.GetIntersection(ltv_map_two).GetHash(),
            ltv_map_common.GetHash());
  ASSERT_EQ(ltv_map_two.GetIntersection(ltv_map_one), ltv_map_common);
}

}  // namespace types
}  // namespace le_audio