Repository: plabayo/rama (language: rust)

Issue:
Len 0 values logged as 0x in responses
When certain TLS extensions of len 0 are used in the Client Hello of a request, rama returns `0x`.

Example for a TLS 1.2 curl request to `echo.ramaproxy.org`:
```
"tls": {
             ...
      {
        "data": "0x",
        "id": "NEXT_PROTOCOL_NEGOTIATION (0x3374)"
      },
      {
        "data": [
          "h2",
          "http/1.1"
        ],
        "id": "APPLICATION_LAYER_PROTOCOL_NEGOTIATION (0x0010)"
      },
      {
        "data": "0x",
        "id": "ENCRYPT_THEN_MAC (0x0016)"
      },
      {
        "data": "0x",
        "id": "EXTENDED_MASTER_SECRET (0x0017)"
      }
}
```


Discussion / hints:
Good catch. Is this something you are willing to pickup?

For those that want to pick it up, here are the locations to start your patch journey:

* echo service: https://github.com/plabayo/rama/blob/d0ca23207654488f7694ef0126fd625971c4ce04/src/cli/service/echo.rs#L419
* fp service: turn this data object into an option: https://github.com/plabayo/rama/blob/d0ca23207654488f7694ef0126fd625971c4ce04/rama-cli/src/cmd/fp/data.rs#L261 which will allow you to omit it if no data

@GlenDC I would like to have a try, could you please assign to me?
Sure thing. All yours. Do let me know if you need help.

Fix patch:
diff --git a/rama-cli/src/cmd/fp/data.rs b/rama-cli/src/cmd/fp/data.rs
index 3ae00dba..424db958 100644
--- a/rama-cli/src/cmd/fp/data.rs
+++ b/rama-cli/src/cmd/fp/data.rs
@@ -258,7 +258,7 @@ pub(super) struct Ja3DisplayInfo {
 #[derive(Debug, Clone, Serialize)]
 pub(super) struct TlsDisplayInfoExtension {
     pub(super) id: String,
-    pub(super) data: TlsDisplayInfoExtensionData,
+    pub(super) data: Option<TlsDisplayInfoExtensionData>,
 }
 
 #[derive(Debug, Clone, Serialize)]
@@ -306,50 +306,52 @@ pub(super) fn get_tls_display_info(ctx: &Context<Arc<State>>) -> Option<TlsDispl
             .map(|extension| match extension {
                 ClientHelloExtension::ServerName(domain) => TlsDisplayInfoExtension {
                     id: extension.id().to_string(),
-                    data: TlsDisplayInfoExtensionData::Single(match domain {
-                        Some(domain) => domain.to_string(),
-                        None => "".to_owned(),
-                    }),
+                    data: domain
+                        .as_ref()
+                        .map(|d| TlsDisplayInfoExtensionData::Single(d.to_string())),
                 },
                 ClientHelloExtension::SignatureAlgorithms(v) => TlsDisplayInfoExtension {
                     id: extension.id().to_string(),
-                    data: TlsDisplayInfoExtensionData::Multi(
+                    data: Some(TlsDisplayInfoExtensionData::Multi(
                         v.iter().map(|s| s.to_string()).collect(),
-                    ),
+                    )),
                 },
                 ClientHelloExtension::SupportedVersions(v) => TlsDisplayInfoExtension {
                     id: extension.id().to_string(),
-                    data: TlsDisplayInfoExtensionData::Multi(
+                    data: Some(TlsDisplayInfoExtensionData::Multi(
                         v.iter().map(|s| s.to_string()).collect(),
-                    ),
+                    )),
                 },
                 ClientHelloExtension::ApplicationLayerProtocolNegotiation(v) => {
                     TlsDisplayInfoExtension {
                         id: extension.id().to_string(),
-                        data: TlsDisplayInfoExtensionData::Multi(
+                        data: Some(TlsDisplayInfoExtensionData::Multi(
                             v.iter().map(|s| s.to_string()).collect(),
-                        ),
+                        )),
                     }
                 }
                 ClientHelloExtension::SupportedGroups(v) => TlsDisplayInfoExtension {
                     id: extension.id().to_string(),
-                    data: TlsDisplayInfoExtensionData::Multi(
+                    data: Some(TlsDisplayInfoExtensionData::Multi(
                         v.iter().map(|s| s.to_string()).collect(),
-                    ),
+                    )),
                 },
                 ClientHelloExtension::ECPointFormats(v) => TlsDisplayInfoExtension {
                     id: extension.id().to_string(),
-                    data: TlsDisplayInfoExtensionData::Multi(
+                    data: Some(TlsDisplayInfoExtensionData::Multi(
                         v.iter().map(|s| s.to_string()).collect(),
-                    ),
+                    )),
                 },
                 ClientHelloExtension::Opaque { id, data } => TlsDisplayInfoExtension {
                     id: id.to_string(),
-                    data: TlsDisplayInfoExtensionData::Single(if data.is_empty() {
-                        "EMPTY".to_owned()
+                    data: if data.is_empty() {
+                        None
                     } else {
-                        format!("0x{}", hex::encode(data))
-                    }),
+                        Some(TlsDisplayInfoExtensionData::Single(format!(
+                            "0x{}",
+                            hex::encode(data)
+                        )))
+                    },
                 },
             })
             .collect::<Vec<_>>(),
diff --git a/rama-cli/src/cmd/fp/endpoints.rs b/rama-cli/src/cmd/fp/endpoints.rs
index 9b086c67..8076bf2c 100644
--- a/rama-cli/src/cmd/fp/endpoints.rs
+++ b/rama-cli/src/cmd/fp/endpoints.rs
@@ -547,18 +547,19 @@ impl From<TlsDisplayInfo> for Vec<Table> {
             ],
         });
         for extension in info.extensions {
+            let mut rows = vec![("ID".to_owned(), extension.id)];
+            if let Some(data) = extension.data {
+                rows.push((
+                    "Data".to_owned(),
+                    match data {
+                        TlsDisplayInfoExtensionData::Single(s) => s,
+                        TlsDisplayInfoExtensionData::Multi(v) => v.join(", "),
+                    },
+                ));
+            }
             vec.push(Table {
                 title: "🔒 TLS Client Hello — Extension".to_owned(),
-                rows: vec![
-                    ("ID".to_owned(), extension.id),
-                    (
-                        "Data".to_owned(),
-                        match extension.data {
-                            TlsDisplayInfoExtensionData::Single(s) => s,
-                            TlsDisplayInfoExtensionData::Multi(v) => v.join(", "),
-                        },
-                    ),
-                ],
+                rows,
             });
         }
         vec
diff --git a/src/cli/service/echo.rs b/src/cli/service/echo.rs
index d1d12bed..dd3162d4 100644
--- a/src/cli/service/echo.rs
+++ b/src/cli/service/echo.rs
@@ -414,10 +414,16 @@ impl Service<(), Request> for EchoService {
                             "id": extension.id().to_string(),
                             "data": v.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
                         }),
-                        ClientHelloExtension::Opaque { id, data } => json!({
-                            "id": id.to_string(),
-                            "data": format!("0x{}", hex::encode(data)),
-                        }),
+                        ClientHelloExtension::Opaque { id, data } => if data.is_empty() {
+                            json!({
+                                "id": id.to_string()
+                            })
+                        } else {
+                            json!({
+                                "id": id.to_string(),
+                                "data": format!("0x{}", hex::encode(data))
+                            })
+                        },
                     }).collect::<Vec<_>>(),
                     "ja3": ja3,
                     "ja4": ja4,


Test patch: