Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMLibStorage.cmake
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
FIND_PACKAGE(CMLIB REQUIRED COMPONENTS CMCONF)

CMCONF_INIT_SYSTEM(FLEET_PROTOCOL)

SET(STORAGE_LIST DEP)

SET(STORAGE_LIST_DEP "https://github.com/bacpack-system/package-tracker.git")
11 changes: 7 additions & 4 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ CMAKE_MINIMUM_REQUIRED(VERSION 3.25 FATAL_ERROR)
PROJECT(fleet-protocol C CXX)

SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD 23)

SET(FLEET_PROTOCOL_CXX_HELPERS_VERSION 1.1.2)
SET(FLEET_PROTOCOL_CXX_HELPERS_VERSION 1.2.0)
SET(FLEET_PROTOCOL_INTERFACE_VERSION 2.0.0)

FIND_PACKAGE(CMLIB
Expand Down Expand Up @@ -40,20 +40,23 @@ IF (BRINGAUTO_PACKAGE)
ENDIF ()

FIND_PACKAGE(fleet-protocol-interface ${FLEET_PROTOCOL_INTERFACE_VERSION} REQUIRED)
FIND_PACKAGE(aeron 1.48.6 REQUIRED)
FIND_PACKAGE(async-function-execution-shared 1.0.0 REQUIRED)

FILE(GLOB_RECURSE source_files ${CMAKE_CURRENT_LIST_DIR}/source/*)

CMDEF_ADD_LIBRARY(
LIBRARY_GROUP ${MODULE_MAINTAINER_TARGET_NAME}
TYPE STATIC
SOURCES ${source_files}
INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/include/"
INCLUDE_DIRECTORIES "${CMAKE_CURRENT_LIST_DIR}/include"
INSTALL_INCLUDE_DIRECTORIES "include/"
VERSION ${FLEET_PROTOCOL_CXX_HELPERS_VERSION}
)

TARGET_LINK_LIBRARIES(${MODULE_MAINTAINER_TARGET_NAME}-static PUBLIC
${COMMON_HEADERS_TARGET}
async-function-execution-shared::async-function-execution-shared
)

ADD_LIBRARY(${MODULE_MAINTAINER_ALIAS_NAME} ALIAS "${MODULE_MAINTAINER_TARGET_NAME}-static")
Expand All @@ -77,5 +80,5 @@ ENDIF ()

IF (BRINGAUTO_TESTS)
ENABLE_TESTING()
ADD_SUBDIRECTORY(${CMAKE_CURRENT_SOURCE_DIR}/test/)
INCLUDE(${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt)
ENDIF (BRINGAUTO_TESTS)
2 changes: 2 additions & 0 deletions cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
SET(CMAKE_FIND_USE_CMAKE_SYSTEM_PATH FALSE)
BA_PACKAGE_LIBRARY(fleet-protocol-interface v${FLEET_PROTOCOL_INTERFACE_VERSION} NO_DEBUG ON)
BA_PACKAGE_LIBRARY(aeron v1.48.6)
BA_PACKAGE_LIBRARY(async-function-execution v1.0.0)
Comment on lines +3 to +4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Ensure BA package names match the CMake find_package usage

Adding aeron v1.48.6 and async-function-execution v1.0.0 here looks consistent with the PR description, but please confirm that:

  • The BA package names (aeron, async-function-execution) correspond to the actual package IDs in your package tracker.
  • The name used here for async-function-execution matches the one used in find_package (e.g., async-function-execution-shared in CMakeLists, if that’s what you use), so CMLIB resolves the same artifact.

You can grep for how these libraries are consumed:


🏁 Script executed:

#!/bin/bash
rg -n "async-function-execution" .
rg -n "aeron" .

Length of output: 392


Fix package name mismatch for async-function-execution-shared

The verification reveals a critical inconsistency: cmake/Dependencies.cmake:4 defines BA_PACKAGE_LIBRARY(async-function-execution v1.0.0), but CMakeLists.txt:44 calls FIND_PACKAGE(async-function-execution-shared 1.0.0 REQUIRED).

The BA package name must match what find_package resolves. Change line 4 in cmake/Dependencies.cmake from async-function-execution to async-function-execution-shared. The aeron entry (line 3) is correctly named.

🤖 Prompt for AI Agents
In cmake/Dependencies.cmake around lines 3 to 4, the BA package name for the
async library is incorrect: line 4 currently registers
"async-function-execution" but CMakeLists.txt expects
"async-function-execution-shared"; update line 4 to use
BA_PACKAGE_LIBRARY(async-function-execution-shared v1.0.0) so the BA package
name matches the FIND_PACKAGE call, leaving the aeron entry unchanged.


IF (BRINGAUTO_TESTS)
BA_PACKAGE_LIBRARY(gtest v1.12.1)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
#pragma once

#include <bringauto/fleet_protocol/cxx/ConvertibleBuffer.hpp>
#include <bringauto/fleet_protocol/cxx/ConvertibleBufferReturn.hpp>

#include <bringauto/async_function_execution/AsyncFunctionExecutor.hpp>


namespace bringauto::fleet_protocol::cxx {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please move it to different namesapace. not cx.


inline static const async_function_execution::FunctionDefinition getModuleNumberAsync {
async_function_execution::FunctionId { 0 },
async_function_execution::Return { int {} },
async_function_execution::Arguments {}
};

inline static const async_function_execution::FunctionDefinition isDeviceTypeSupportedAsync {
async_function_execution::FunctionId { 1 },
async_function_execution::Return { int {} },
async_function_execution::Arguments { uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition sendStatusConditionAsync {
async_function_execution::FunctionId { 2 },
async_function_execution::Return { int {} },
async_function_execution::Arguments { ConvertibleBuffer {}, ConvertibleBuffer {}, uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition generateCommandAsync {
async_function_execution::FunctionId { 3 },
async_function_execution::Return { ConvertibleBufferReturn {} },
async_function_execution::Arguments { ConvertibleBuffer {}, ConvertibleBuffer {}, ConvertibleBuffer {}, uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition aggregateStatusAsync {
async_function_execution::FunctionId { 4 },
async_function_execution::Return { ConvertibleBufferReturn {} },
async_function_execution::Arguments { ConvertibleBuffer {}, ConvertibleBuffer {}, uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition aggregateErrorAsync {
async_function_execution::FunctionId { 5 },
async_function_execution::Return { ConvertibleBufferReturn {} },
async_function_execution::Arguments { ConvertibleBuffer {}, ConvertibleBuffer {}, uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition generateFirstCommandAsync {
async_function_execution::FunctionId { 6 },
async_function_execution::Return { ConvertibleBufferReturn {} },
async_function_execution::Arguments { uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition statusDataValidAsync {
async_function_execution::FunctionId { 7 },
async_function_execution::Return { int {} },
async_function_execution::Arguments { ConvertibleBuffer {}, uint32_t {} }
};

inline static const async_function_execution::FunctionDefinition commandDataValidAsync {
async_function_execution::FunctionId { 8 },
async_function_execution::Return { int {} },
async_function_execution::Arguments { ConvertibleBuffer {}, uint32_t {} }
};

/**
* @brief Type alias for an AsyncFunctionExecutor specialized with module-related functions.
*/
using ModuleFunctionExecutor = async_function_execution::AsyncFunctionExecutor<
decltype(getModuleNumberAsync),
decltype(isDeviceTypeSupportedAsync),
decltype(sendStatusConditionAsync),
decltype(generateCommandAsync),
decltype(aggregateStatusAsync),
decltype(aggregateErrorAsync),
decltype(generateFirstCommandAsync),
decltype(statusDataValidAsync),
decltype(commandDataValidAsync)
>;

/**
* @brief FunctionList containing all module-related function definitions.
*/
inline static const async_function_execution::FunctionList<
decltype(getModuleNumberAsync),
decltype(isDeviceTypeSupportedAsync),
decltype(sendStatusConditionAsync),
decltype(generateCommandAsync),
decltype(aggregateStatusAsync),
decltype(aggregateErrorAsync),
decltype(generateFirstCommandAsync),
decltype(statusDataValidAsync),
decltype(commandDataValidAsync)
> moduleFunctionList {
getModuleNumberAsync,
isDeviceTypeSupportedAsync,
sendStatusConditionAsync,
generateCommandAsync,
aggregateStatusAsync,
aggregateErrorAsync,
generateFirstCommandAsync,
statusDataValidAsync,
commandDataValidAsync
};

}
32 changes: 32 additions & 0 deletions include/bringauto/fleet_protocol/cxx/ConvertibleBuffer.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#pragma once

#include <fleet_protocol/common_headers/device_management.h>

#include <cstdint>
#include <span>



namespace bringauto::fleet_protocol::cxx {

/**
* @brief ConvertibleBuffer is a helper class to convert a fleet_protocol 'buffer' struct
* to/from a byte span for serialization/deserialization.
*/
struct ConvertibleBuffer final {
struct ::buffer buffer {};
ConvertibleBuffer() = default;
ConvertibleBuffer(struct ::buffer buff) : buffer(buff) {}

std::span<const uint8_t> serialize() const {
return std::span {reinterpret_cast<const uint8_t *>(buffer.data), buffer.size_in_bytes};
}
void deserialize(std::span<const uint8_t> bytes) {
auto size = bytes.size();
buffer.data = new uint8_t[size];
buffer.size_in_bytes = size;
std::memcpy(buffer.data, bytes.data(), size);
}
Comment on lines 3 to 29
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Clarify shallow, non-owning deserialize semantics and verify header include path

  • The deserialize method:

    void deserialize(std::span<const uint8_t> bytes) {
        buffer.data = const_cast<uint8_t *>(bytes.data());
        buffer.size_in_bytes = bytes.size();
    }

    performs a shallow conversion: buffer ends up pointing directly into the bytes span with no copy. This is consistent with the pattern used in related projects (const-correct wrapper over a C buffer plus const_cast for performance), but it means:

    • ConvertibleBuffer does not own the memory.
    • buffer.data becomes invalid as soon as the source span’s underlying storage is destroyed or modified.

    I’d strongly recommend making this explicit in the doc comment (e.g., that deserialize aliases the span and that the caller must ensure the lifetime of the underlying data) to avoid misuse and accidental dangling pointers.
    Based on learnings

  • Static analysis reports 'fleet_protocol/common_headers/device_management.h' file not found at Line 3. That may just be a tooling include-path issue, but please double-check that:

    • The header really lives under fleet_protocol/common_headers/device_management.h in your install layout, and
    • Your CMake includes the appropriate include directories when building this target.

    If the installed path differs (e.g., an extra bringauto/ prefix), updating the include or include dirs here will prevent build failures.

To sanity-check usage and ensure expectations around ownership are clear, you can locate all call sites:


🏁 Script executed:

#!/bin/bash
rg -n "ConvertibleBuffer" include test

Length of output: 2829


I need to gather more information. Let me search for actual deserialize calls and verify the include path.

Now I'll search the repository directly for the necessary information:

Based on my analysis of the code and the learnings from the module-gateway project, I can now generate the rewritten review comment:

Add documentation for shallow, non-owning deserialize semantics and verify include path

The deserialize method creates a non-owning alias to the input span via const_cast. While this pattern is intentional for performance, the doc comment should explicitly state:

  • ConvertibleBuffer does not own the aliased memory
  • The caller must ensure the underlying span's data remains valid for the ConvertibleBuffer's lifetime

For the include path at line 3 (<fleet_protocol/common_headers/device_management.h>): verify your CMake configuration includes the correct directories when building this target. If the header is installed at a different path, update either the include or the CMake include_directories accordingly to prevent build failures.


🏁 Script executed:

#!/bin/bash
# Search for deserialize method calls
rg -n "\.deserialize\(" --type cpp include test src 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Find device_management.h file
fd -t f "device_management\.h" 2>/dev/null

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check for CMakeLists or include_directories configuration
fd -t f "CMakeLists\.txt" | head -5

Length of output: 76


🏁 Script executed:

#!/bin/bash
# Look for the buffer struct definition
rg -n "struct.*buffer" --type c --type h -A 5 include | head -50

Length of output: 1709


🏁 Script executed:

#!/bin/bash
# Check if there's a fleet_protocol subdirectory
find . -type d -name "fleet_protocol" 2>/dev/null | head -10

Length of output: 68


I need to check the CMakeLists configuration to understand the include path setup and where device_management comes from:


🏁 Script executed:

#!/bin/bash
# Check main CMakeLists.txt for include configuration
cat CMakeLists.txt | head -80

Length of output: 2559


🏁 Script executed:

#!/bin/bash
# Search for device_management references in build files
rg -i "device_management" --type cmake --type txt 2>/dev/null

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for external dependencies or include_directories declarations
rg -n "include_directories|find_package|add_subdirectory" CMakeLists.txt

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if fleet_protocol is an external dependency or package
rg -n "fleet_protocol\|fleet-protocol" CMakeLists.txt

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for common_headers directory anywhere in the repo
find . -type d -name "common_headers" 2>/dev/null

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Look for buffer struct definition in any header file
rg -n "^\s*struct.*buffer.*{" --type h include

Length of output: 105


🏁 Script executed:

#!/bin/bash
# Check for pkg-config or cmake finds related to fleet-protocol
rg -n "pkg_check|find_package|PkgConfig" CMakeLists.txt

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Examine project structure for dependencies or installed files
ls -la | grep -E "\.cmake|\.pc|conanfile|requirements"

Length of output: 67


🏁 Script executed:

#!/bin/bash
# Check if the include path exists but just with different structure
find . -path "*fleet_protocol*" -name "*.h" 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search all headers in include directory to see what's included
fd -e h . include/bringauto/fleet_protocol/cxx | xargs rg -l "device_management"

Length of output: 173


🏁 Script executed:

#!/bin/bash
# Check what headers are in the common_headers or device_management areas if they exist
find . -path "*fleet_protocol*common*" -o -path "*device_management*" 2>/dev/null

Length of output: 0


Let me verify the external package structure one more time:


🏁 Script executed:

#!/bin/bash
# Check DeviceID.hpp to see how it includes device_management
cat include/bringauto/fleet_protocol/cxx/DeviceID.hpp | head -20

Length of output: 483


🏁 Script executed:

#!/bin/bash
# Look for any documentation or comments about the external dependency
rg -n "fleet-protocol-interface\|common-headers" --type cmake

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check if there's a conan file or requirements
find . -name "conanfile*" -o -name "requirements.txt" -o -name ".conan*" 2>/dev/null

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Check the Dependencies.cmake to understand external packages
cat cmake/Dependencies.cmake 2>/dev/null | head -50

Length of output: 314


Add documentation for shallow, non-owning deserialize semantics

The deserialize method creates a non-owning alias to the input span via const_cast. While this pattern is intentional for performance, the doc comment should explicitly state:

  • ConvertibleBuffer does not own the aliased memory
  • The caller must ensure the underlying span's data remains valid for the ConvertibleBuffer's lifetime

The include path <fleet_protocol/common_headers/device_management.h> is correct—it's provided by the external fleet-protocol-interface dependency that the CMake configuration properly fetches and links.

🧰 Tools
🪛 Clang (14.0.6)

[error] 3-3: 'fleet_protocol/common_headers/device_management.h' file not found

(clang-diagnostic-error)

🤖 Prompt for AI Agents
In include/bringauto/fleet_protocol/cxx/ConvertibleBuffer.hpp around lines 3 to
27, the deserialize method currently creates a shallow, non-owning alias to the
input span via const_cast without any documentation; update the header comment
for deserialize (and/or class-level doc) to explicitly state that
ConvertibleBuffer does not take ownership of the aliased memory, that
deserialize performs a non-owning aliasing (uses const_cast) for performance,
and that the caller is responsible for ensuring the input span's backing storage
remains valid for the entire lifetime of the ConvertibleBuffer instance.

};

}
42 changes: 42 additions & 0 deletions include/bringauto/fleet_protocol/cxx/ConvertibleBufferReturn.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#pragma once

#include <fleet_protocol/common_headers/device_management.h>

#include <cstdint>
#include <cstring>
#include <span>



namespace bringauto::fleet_protocol::cxx {

/**
* @brief ConvertibleBufferReturn is a helper class to convert a fleet_protocol 'buffer' struct
* and a return code to/from a byte span for serialization/deserialization.
* The first 4 bytes represent the return code (int), followed by the buffer data.
*/
struct ConvertibleBufferReturn final {
int returnCode {};
struct ::buffer buffer {};
ConvertibleBufferReturn() = default;
ConvertibleBufferReturn(int code, struct ::buffer buff) : returnCode(code), buffer(buff) {}

std::span<const uint8_t> serialize() const {
size_t total_size = sizeof(int) + buffer.size_in_bytes;
uint8_t* data = new uint8_t[total_size];
std::memcpy(data, &returnCode, sizeof(int));
std::memcpy(data + sizeof(int), buffer.data, buffer.size_in_bytes);
return {data, total_size};
}
void deserialize(std::span<const uint8_t> bytes) {
auto size = bytes.size();
if (size < sizeof(int)) return;
std::memcpy(&returnCode, bytes.data(), sizeof(int));
size -= sizeof(int);
buffer.data = new uint8_t[size];
buffer.size_in_bytes = size;
std::memcpy(buffer.data, bytes.data() + sizeof(int), size);
}
Comment on lines 18 to 39
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix ownership/lifetime in serialize/deserialize (current code leaks and risks double-ownership)

serialize() allocates with new[] and returns a std::span<const uint8_t> that has no way to free the memory, so every call leaks. deserialize() also allocates with new[] into buffer.data without any corresponding delete/free and will leak or overwrite pointers if called multiple times. This is also inconsistent with the existing shallow-aliasing pattern used by ConvertibleBuffer::deserialize.

A safer pattern is:

  • Use an internal owning buffer for serialization, e.g. a mutable std::vector<uint8_t> member, and return a span over that.
  • In deserialize(), avoid allocating and instead shallow-copy into buffer (as ConvertibleBuffer does), pointing into the supplied bytes span:
-struct ConvertibleBufferReturn final {
-    int returnCode {};
-    struct ::buffer buffer {};
+struct ConvertibleBufferReturn final {
+    int returnCode {};
+    struct ::buffer buffer {};
+    mutable std::vector<uint8_t> serializedData{};
@@
-    std::span<const uint8_t> serialize() const {
-        size_t total_size = sizeof(int) + buffer.size_in_bytes;
-        uint8_t* data = new uint8_t[total_size];
-        std::memcpy(data, &returnCode, sizeof(int));
-        std::memcpy(data + sizeof(int), buffer.data, buffer.size_in_bytes);
-        return {data, total_size};
-    }
-    void deserialize(std::span<const uint8_t> bytes) {
-        auto size = bytes.size();
-        if (size < sizeof(int)) return;
-        std::memcpy(&returnCode, bytes.data(), sizeof(int));
-        size -= sizeof(int);
-        buffer.data = new uint8_t[size];
-        buffer.size_in_bytes = size;
-        std::memcpy(buffer.data, bytes.data() + sizeof(int), size);
-        buffer.size_in_bytes = size;
-    }
+    std::span<const uint8_t> serialize() const {
+        const auto headerSize = sizeof(returnCode);
+        const auto payloadSize = buffer.size_in_bytes;
+        serializedData.resize(headerSize + payloadSize);
+        std::memcpy(serializedData.data(), &returnCode, headerSize);
+        std::memcpy(serializedData.data() + headerSize, buffer.data, payloadSize);
+        return {serializedData.data(), serializedData.size()};
+    }
+
+    void deserialize(std::span<const uint8_t> bytes) {
+        const auto size = bytes.size();
+        const auto headerSize = sizeof(returnCode);
+        if (size < headerSize) {
+            return;
+        }
+
+        std::memcpy(&returnCode, bytes.data(), headerSize);
+        const auto payloadSize = size - headerSize;
+        buffer.data = const_cast<uint8_t *>(bytes.data() + headerSize);
+        buffer.size_in_bytes = payloadSize;
+    }

This eliminates the leaks and matches the shallow-copy semantics already used for ConvertibleBuffer, assuming the caller guarantees the lifetime of the backing storage for the bytes span. Based on learnings.


I'll verify the review comment by examining the codebase structure and comparing the current implementation against the pattern used elsewhere.


🏁 Script executed:

#!/bin/bash
# 1. Locate the file and confirm it exists
fd -t f ConvertibleBufferReturn.hpp

# 2. Find ConvertibleBuffer to compare patterns
fd -t f ConvertibleBuffer.hpp

# 3. Search for ::buffer struct definition
rg -n "struct\s+::?buffer" --type=cpp --type=hpp -A 5

Length of output: 151


🏁 Script executed:

#!/bin/bash
# 4. Check for destructors or cleanup in ConvertibleBufferReturn
rg -n "ConvertibleBufferReturn" --type=cpp --type=hpp -C 3

# 5. Look for usages of serialize() to understand lifetime
rg -n "\.serialize\(\)" --type=cpp --type=hpp -C 2

Length of output: 55


🏁 Script executed:

#!/bin/bash
# 6. Examine ConvertibleBuffer::deserialize implementation to confirm shallow aliasing
ast-grep --pattern $'struct ConvertibleBuffer {
  $$$
  deserialize($_) {
    $$$
  }
  $$$
}'

Length of output: 0


🏁 Script executed:

#!/bin/bash
# View the two key files directly
echo "=== ConvertibleBufferReturn.hpp ==="
cat include/bringauto/fleet_protocol/cxx/ConvertibleBufferReturn.hpp

echo ""
echo "=== ConvertibleBuffer.hpp ==="
cat include/bringauto/fleet_protocol/cxx/ConvertibleBuffer.hpp

Length of output: 2142


🏁 Script executed:

#!/bin/bash
# Search for deserialize patterns without type restrictions
echo "=== Usages of deserialize ==="
rg -n "deserialize" -C 3

echo ""
echo "=== Struct ::buffer definition ==="
rg -n "struct.*buffer" -C 2

Length of output: 5393


🏁 Script executed:

#!/bin/bash
# Check for usage of serialize and any cleanup/deleter patterns
echo "=== Serialize usages ==="
rg -n "serialize\(\)" -C 2

echo ""
echo "=== Check for delete or std::unique_ptr around buffer ==="
rg -n "delete.*buffer|std::unique_ptr.*buffer" -C 2

Length of output: 1653


Fix memory leaks in serialize() and deserialize() — use internal buffer and shallow aliasing

The review comment correctly identifies critical memory leaks:

  • serialize() allocates with new[] and returns a std::span<const uint8_t> with no deleter; every call leaks the heap-allocated memory.
  • deserialize() allocates with new[] into buffer.data and never deletes; repeated calls leak and risk double-free.

Verified file state matches the snippet exactly. The implementation also diverges from the existing ConvertibleBuffer pattern, which intentionally uses shallow aliasing (const_cast) in deserialize() and no allocation in serialize() — an approach already documented in the codebase as intentional for performance.

The proposed fix—internal owning mutable std::vector<uint8_t> for serialization and shallow aliasing in deserialize()—is sound and aligns with the established shallow-copy semantics used by ConvertibleBuffer. This ensures proper lifetime management without repeated allocations and avoids dangling pointers.

🤖 Prompt for AI Agents
In include/bringauto/fleet_protocol/cxx/ConvertibleBufferReturn.hpp around lines
18-40, the current serialize() and deserialize() leak heap memory and diverge
from the project's shallow-aliasing ConvertibleBuffer pattern; replace the raw
new[] allocations by adding a mutable internal owning std::vector<uint8_t>
member used for serialization (fill it with sizeof(int)+buffer.size_in_bytes,
copy returnCode then buffer bytes into it, and return a std::span pointing at
vector.data()), and change deserialize() to perform shallow aliasing (do not
allocate; set buffer.data to point into the incoming span + sizeof(int) via
const_cast<uint8_t*> and set buffer.size_in_bytes accordingly) so lifetimes
match the documented pattern and no raw new/delete is used.

};

}
2 changes: 1 addition & 1 deletion test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.25 FATAL_ERROR)
PROJECT(fleet-protocol C CXX)

SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD 23)

ADD_SUBDIRECTORY(${CMAKE_CURRENT_LIST_DIR}/lib/memory_management/)

Expand Down
2 changes: 1 addition & 1 deletion test/lib/memory_management/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CMAKE_MINIMUM_REQUIRED(VERSION 3.25 FATAL_ERROR)
PROJECT(fleet-protocol C CXX)

SET(CMAKE_CXX_STANDARD 20)
SET(CMAKE_CXX_STANDARD 23)

FIND_PACKAGE(fleet-protocol-interface 2.0.0 REQUIRED)

Expand Down
18 changes: 18 additions & 0 deletions test/source/AsyncModuleFunctionDefinitionsTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <bringauto/fleet_protocol/cxx/AsyncModuleFunctionDefinitions.hpp>

#include <gtest/gtest.h>

using namespace bringauto::fleet_protocol::cxx;


TEST(AsyncModuleFunctionDefinitions_tests, FunctionDefinitionsExist) {
EXPECT_EQ(getModuleNumberAsync.id.value, 0);
EXPECT_EQ(isDeviceTypeSupportedAsync.id.value, 1);
EXPECT_EQ(sendStatusConditionAsync.id.value, 2);
EXPECT_EQ(generateCommandAsync.id.value, 3);
EXPECT_EQ(aggregateStatusAsync.id.value, 4);
EXPECT_EQ(aggregateErrorAsync.id.value, 5);
EXPECT_EQ(generateFirstCommandAsync.id.value, 6);
EXPECT_EQ(statusDataValidAsync.id.value, 7);
EXPECT_EQ(commandDataValidAsync.id.value, 8);
}