This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hertzbeat.git


The following commit(s) were added to refs/heads/master by this push:
     new 33284bb955 [fix] strip NUL bytes before tdengine history insert (#4310)
33284bb955 is described below

commit 33284bb95553719782b8084161c23c8ecdaa89ae
Author: NekoPunch <[email protected]>
AuthorDate: Sun Aug 16 22:46:55 2026 -0700

    [fix] strip NUL bytes before tdengine history insert (#4310)
    
    Co-authored-by: aias00 <[email protected]>
---
 .../history/tsdb/tdengine/TdEngineDataStorage.java | 10 +++++---
 .../warehouse/store/TdEngineDataStorageTest.java   | 29 +++++++++++++++++++---
 2 files changed, 32 insertions(+), 7 deletions(-)

diff --git 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/tdengine/TdEngineDataStorage.java
 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/tdengine/TdEngineDataStorage.java
index 361d45dbbb..94eb19413d 100644
--- 
a/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/tdengine/TdEngineDataStorage.java
+++ 
b/hertzbeat-warehouse/src/main/java/org/apache/hertzbeat/warehouse/store/history/tsdb/tdengine/TdEngineDataStorage.java
@@ -66,6 +66,7 @@ public class TdEngineDataStorage extends 
AbstractHistoryDataStorage {
 
     private static final String CONSTANTS_URL_PREFIX = "jdbc:TAOS-RS://";
     private static final Pattern SQL_SPECIAL_STRING_PATTERN = 
Pattern.compile("(\\\\)|(')");
+    private static final Pattern NUL_CHAR_PATTERN = Pattern.compile("\\u0000");
     private static final String INSTANCE_NULL = "''";
     private static final String CONSTANTS_CREATE_DATABASE = "CREATE DATABASE 
IF NOT EXISTS %s";
     private static final String INSERT_TABLE_DATA_SQL = "INSERT INTO `%s` 
USING `%s` TAGS (%s) VALUES %s";
@@ -346,12 +347,13 @@ public class TdEngineDataStorage extends 
AbstractHistoryDataStorage {
     }
 
     private String formatStringValue(String value) {
-        String formatValue = 
SQL_SPECIAL_STRING_PATTERN.matcher(value).replaceAll("\\\\$0");
-        // bugfix Argument list too long
-        if (formatValue != null && formatValue.length() > 
tableStrColumnDefineMaxLength) {
+        // snmp octet strings may carry NUL padding that breaks the insert sql
+        String formatValue = NUL_CHAR_PATTERN.matcher(value).replaceAll("");
+        // truncate the logical value before escaping so the cut cannot split 
an escape sequence
+        if (formatValue.length() > tableStrColumnDefineMaxLength) {
             formatValue = formatValue.substring(0, 
tableStrColumnDefineMaxLength);
         }
-        return formatValue;
+        return 
SQL_SPECIAL_STRING_PATTERN.matcher(formatValue).replaceAll("\\\\$0");
     }
 
     @Override
diff --git 
a/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/store/TdEngineDataStorageTest.java
 
b/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/store/TdEngineDataStorageTest.java
index 97f94090c6..2c3327f87e 100644
--- 
a/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/store/TdEngineDataStorageTest.java
+++ 
b/hertzbeat-warehouse/src/test/java/org/apache/hertzbeat/warehouse/store/TdEngineDataStorageTest.java
@@ -115,6 +115,25 @@ class TdEngineDataStorageTest {
         assertTrue(executedSql.matches(".*VALUES\\s+\\(\\d+.*68\\.7\\)"), 
"Should contain timestamp and value 68.7");
     }
 
+    @Test
+    void testSaveDataStripsControlCharacters() throws Exception {
+        tdEngineDataStorage = new TdEngineDataStorage(tdEngineProperties);
+        setPrivateField(tdEngineDataStorage, "hikariDataSource", 
mockHikariDataSource);
+        setParentPrivateField(tdEngineDataStorage, "serverAvailable", true);
+
+        // snmp octet strings can carry NUL bytes (issue #1481); tabs are 
legitimate data and stay
+        CollectRep.MetricsData metricsData = 
generateMockedMetricsData("Loopback\tInterface 1\u0000");
+        tdEngineDataStorage.saveData(metricsData);
+
+        ArgumentCaptor<String> sqlCaptor = 
ArgumentCaptor.forClass(String.class);
+        verify(mockStatement, atLeastOnce()).execute(sqlCaptor.capture());
+        String executedSql = sqlCaptor.getValue();
+
+        assertTrue(executedSql.contains("Loopback\tInterface 1'"), "NUL 
stripped, tab preserved");
+        assertTrue(executedSql.indexOf('\u0000') < 0, "no raw NUL byte may 
reach the sql text");
+        assertTrue(!executedSql.contains("\\u0000"), "no textual NUL escape 
may survive in the labels json");
+    }
+
     @Test
     void destroy() {
     }
@@ -146,6 +165,10 @@ class TdEngineDataStorageTest {
     }
 
     public static CollectRep.MetricsData generateMockedMetricsData() {
+        return generateMockedMetricsData("test-%server-01");
+    }
+
+    public static CollectRep.MetricsData generateMockedMetricsData(String 
instanceValue) {
         CollectRep.MetricsData mockMetricsData = 
Mockito.mock(CollectRep.MetricsData.class);
 
         when(mockMetricsData.getId()).thenReturn(0L);
@@ -156,9 +179,9 @@ class TdEngineDataStorageTest {
         when(mockMetricsData.getInstance()).thenReturn("test-%server-01");
 
         CollectRep.ValueRow mockValueRow = 
Mockito.mock(CollectRep.ValueRow.class);
-        List<String> columnValues = List.of("test-%server-01", "68.7");
+        List<String> columnValues = List.of(instanceValue, "68.7");
         when(mockValueRow.getColumnsList()).thenReturn(columnValues);
-        when(mockValueRow.getColumns(0)).thenReturn("test-%server-01");
+        when(mockValueRow.getColumns(0)).thenReturn(instanceValue);
         when(mockValueRow.getColumns(1)).thenReturn("68.7");
         List<CollectRep.ValueRow> mockValueRowsList = List.of(mockValueRow);
         when(mockMetricsData.getValues()).thenReturn(mockValueRowsList);
@@ -177,7 +200,7 @@ class TdEngineDataStorageTest {
         Field instanceArrowField = new Field("instance", instanceFieldType, 
null);
         ArrowCell instanceCell = Mockito.mock(ArrowCell.class);
         when(instanceCell.getField()).thenReturn(instanceArrowField);
-        when(instanceCell.getValue()).thenReturn("test-%server-01");
+        when(instanceCell.getValue()).thenReturn(instanceValue);
         
when(instanceCell.getMetadataAsBoolean(MetricDataConstants.LABEL)).thenReturn(true);
         
when(instanceCell.getMetadataAsByte(MetricDataConstants.TYPE)).thenReturn(CommonConstants.TYPE_STRING);
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to