Repository: envoyproxy/envoy
File: source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc (C++)

Code before:
      } else {
        (*tlvs_metadata.mutable_typed_metadata())[key_value_pair->key()] = tlv_value;
        Protobuf::Any typed_metadata;
        typed_metadata.PackFrom(tlvs_metadata);
        cb_->setDynamicTypedMetadata(metadata_key, typed_metadata);
      }
      // Always populate untyped metadata for backwards compatibility.
      Protobuf::Value metadata_value;
      // Sanitize any non utf8 characters.
      auto sanitised_tlv_value = MessageUtil::sanitizeUtf8String(tlv_value);
      metadata_value.set_string_value(sanitised_tlv_value.data(), sanitised_tlv_value.size());
      Protobuf::Struct metadata((*cb_->dynamicMetadata().mutable_filter_metadata())[metadata_key]);
      metadata.mutable_fields()->insert({key_value_pair->key(), metadata_value});
      cb_->setDynamicMetadata(metadata_key, metadata);

      std::string filter_state_key =
          absl::StrCat("envoy.network.proxy_protocol.tlv.", key_value_pair->key());
      if (!cb_->filterState().hasDataWithName(filter_state_key)) {
        std::string sanitised_value(sanitised_tlv_value.data(), sanitised_tlv_value.size());
        cb_->filterState().setData(filter_state_key,
                                   std::make_unique<Router::StringAccessorImpl>(sanitised_value),
                                   StreamInfo::FilterState::StateType::ReadOnly,
                                   StreamInfo::FilterState::LifeSpan::Connection);
        ENVOY_LOG(trace, "proxy_protocol: Stored TLV type {} value in FilterState with key {}",
                  tlv_type, filter_state_key);
      }
    } else {
      ENVOY_LOG(trace,
                "proxy_protocol: Skip TLV of type {} since it's not needed for dynamic metadata",
                tlv_type);
    }

    // Save TLVs to the filter state.
    if (config_->isPassThroughTlvTypeNeeded(tlv_type)) {
      ENVOY_LOG(trace, "proxy_protocol: Storing parsed TLV of type {} to filter state.", tlv_type);
      parsed_tlvs_.push_back({tlv_type, {tlv_value.begin(), tlv_value.end()}});
    }

    idx += tlv_value_length;
    ASSERT(idx <= len);
  }
  return true;
}

ReadOrParseState Filter::readExtensions(Network::ListenerFilterBuffer& buffer) {
  auto raw_slice = buffer.rawSlice();
  // waiting for more data if there is no enough data for extensions.
  if (raw_slice.len_ < (proxy_protocol_header_.value().wholeHeaderLength())) {
    ENVOY_LOG(
        trace,
        "waiting for more data to read extensions. Buffer length: {}, extension header length {}",

Review comment (suggestion):
Could we use single filter state for this attributes? Note the filter state could support sub `field`. So, I think it would be better to store a map-like object in the filter state with single key. :)

BTW, could you add a proto API at the proxy_protocol to control this behavior. Like we can add an enum to tell where the data should be stored into:
- DYNAMIC_METADATA (by default to avoid behavior change)
- FILTER_STATE

Code after:
 *        See https://www.haproxy.org/download/2.1/doc/proxy-protocol.txt for details
 */
bool Filter::parseTlvs(const uint8_t* buf, size_t len) {
  size_t idx{0};
  while (idx < len) {
    const uint8_t tlv_type = buf[idx];
    idx++;

    if ((idx + 1) >= len) {
      ENVOY_LOG(debug,
                fmt::format("failed to read proxy protocol extension. No bytes for TLV length. "
                            "Extension length is {}, current index is {}, current type is {}.",
                            len, idx, tlv_type));
      return false;
    }

    const uint8_t tlv_length_upper = buf[idx];
    const uint8_t tlv_length_lower = buf[idx + 1];
    const size_t tlv_value_length = (tlv_length_upper << 8) + tlv_length_lower;
    idx += 2;

    // Get the value.
    if ((idx + tlv_value_length - 1) >= len) {
      ENVOY_LOG(
          debug,
          fmt::format("failed to read proxy protocol extension. No bytes for TLV value. "
                      "Extension length is {}, current index is {}, current type is {}, current "
                      "value length is {}.",
                      len, idx, tlv_type, tlv_length_upper));
      return false;
    }

    // Only save to dynamic metadata if this type of TLV is needed.
    absl::string_view tlv_value(reinterpret_cast<char const*>(buf + idx), tlv_value_length);
    auto key_value_pair = config_->isTlvTypeNeeded(tlv_type);
    if (nullptr != key_value_pair) {
      // Sanitize any non utf8 characters.
      auto sanitised_tlv_value = MessageUtil::sanitizeUtf8String(tlv_value);
      std::string sanitised_value(sanitised_tlv_value.data(), sanitised_tlv_value.size());

      if (config_->tlvLocation() ==
          envoy::extensions::filters::listener::proxy_protocol::v3::ProxyProtocol::FILTER_STATE) {
        // Store TLV values in a single filter state object.
        constexpr absl::string_view kFilterStateKey = "envoy.network.proxy_protocol.tlv";
        TlvFilterStateObject* tlv_filter_state_obj = nullptr;
        const auto* existing_obj = cb_->filterState().getDataReadOnlyGeneric(kFilterStateKey);
        if (existing_obj != nullptr) {
          tlv_filter_state_obj = const_cast<TlvFilterStateObject*>(
              dynamic_cast<const TlvFilterStateObject*>(existing_obj));
        }
        if (tlv_filter_state_obj == nullptr) {