Skip to content

FFTO: PgSQL CommandComplete tag parser extraction + unit tests#5515

Merged
renecannao merged 5 commits intov3.0-5473from
v3.0-5499
Mar 22, 2026
Merged

FFTO: PgSQL CommandComplete tag parser extraction + unit tests#5515
renecannao merged 5 commits intov3.0-5473from
v3.0-5499

Conversation

@renecannao
Copy link
Contributor

@renecannao renecannao commented Mar 22, 2026

Implements #5499. Extracts parse_pgsql_command_complete() from PgSQLFFTO — 17 tests covering INSERT/UPDATE/DELETE/SELECT/FETCH/MOVE/COPY/MERGE/CREATE TABLE/edge cases.

Summary by CodeRabbit

  • New Features

    • Added PostgreSQL CommandComplete parsing to report row counts and mark result-set commands (e.g., SELECT/FETCH/MOVE).
    • Added MySQL protocol helpers to decode length-encoded integers and build wire-format packets.
  • Tests

    • Added unit tests covering PostgreSQL tag parsing, MySQL lenenc decoding, packet construction, truncated streams, whitespace/null payloads, and large counts.
  • Chores

    • Updated build and test targets to include the new components and tests.

New files:
- include/PgSQLCommandComplete.h: PgSQLCommandResult struct +
  parse_pgsql_command_complete() declaration
- lib/PgSQLCommandComplete.cpp: implementation mirroring
  extract_pg_rows_affected() from PgSQLFFTO.cpp

Parses PostgreSQL CommandComplete tags (INSERT/UPDATE/DELETE/SELECT/
FETCH/MOVE/COPY/MERGE) to extract row counts. Pure function with
no FFTO or session dependencies.
17 test cases: DML commands (INSERT/UPDATE/DELETE/COPY/MERGE),
SELECT commands (SELECT/FETCH/MOVE), no-row-count commands
(CREATE TABLE/DROP INDEX/BEGIN), edge cases (null, empty,
whitespace, INSERT with OID, large counts).
Copilot AI review requested due to automatic review settings March 22, 2026 11:01
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request refactors the PostgreSQL CommandComplete tag parsing logic by isolating it into a new, dedicated module. This change significantly enhances the testability of the parser by allowing comprehensive unit tests to be written and executed independently. The new tests validate the parser's behavior across a wide range of PostgreSQL command responses, ensuring robustness and correctness.

Highlights

  • Parser Extraction: The parse_pgsql_command_complete function has been extracted from PgSQLFFTO into its own dedicated files (PgSQLCommandComplete.h and PgSQLCommandComplete.cpp). This refactoring improves modularity and testability.
  • Comprehensive Unit Tests: A new unit test suite (pgsql_command_complete_unit-t.cpp) has been added, featuring 17 tests. These tests cover various PostgreSQL CommandComplete tags, including DML operations (INSERT, UPDATE, DELETE, COPY, MERGE), result-set operations (SELECT, FETCH, MOVE), commands without row counts (CREATE TABLE, DROP INDEX, BEGIN), and several edge cases like empty payloads, whitespace padding, and large row counts.
  • Build System Integration: The lib/Makefile and test/tap/tests/unit/Makefile have been updated to include the newly added source and test files, ensuring they are properly compiled and linked into the project.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8f00b95e-bcc1-4037-b756-0a30a97b776e

📥 Commits

Reviewing files that changed from the base of the PR and between 1274fb7 and ea16ebb.

📒 Files selected for processing (2)
  • lib/Makefile
  • test/tap/tests/unit/Makefile

📝 Walkthrough

Walkthrough

Adds a PostgreSQL CommandComplete tag parser, MySQL protocol utility functions, unit tests, and Makefile updates to build and test the new components.

Changes

Cohort / File(s) Summary
PgSQL CommandComplete API & impl
include/PgSQLCommandComplete.h, lib/PgSQLCommandComplete.cpp
New public PgSQLCommandResult struct and parse_pgsql_command_complete() declaration/implementation. Parser trims payload, tokenizes, classifies commands (SELECT/FETCH/MOVE vs DML like INSERT/UPDATE/DELETE/COPY/MERGE), and parses the last token as a base-10 row count; returns {0,false} on parse/format failures.
MySQL protocol utilities
include/MySQLProtocolUtils.h, lib/MySQLProtocolUtils.cpp
New public declarations and implementations: mysql_read_lenenc_int() (advances buffer pointer, decodes MySQL length-encoded integers, returns 0 on truncation/error) and mysql_build_packet() (writes 3-byte little-endian payload length + 1-byte seq-id header and copies payload; returns total written size).
Build targets
lib/Makefile, test/tap/tests/unit/Makefile
Added PgSQLCommandComplete.oo and MySQLProtocolUtils.oo to library objects; added unit test targets pgsql_command_complete_unit-t and ffto_protocol_unit-t to unit test Makefile.
Unit tests
test/tap/tests/unit/pgsql_command_complete_unit-t.cpp, test/tap/tests/unit/ffto_protocol_unit-t.cpp
New tests: CommandComplete parsing across command types and edge cases (null/empty, whitespace, extra tokens, large counts); MySQL lenenc decoding variants, packet building checks, OK-packet affected-rows parsing, and fragmentation/multi-packet simulations.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐰 I nibble bytes in twilight's glow,
Parsing counts that softly show,
SELECTs hop free, DMLs tally more,
Packets stitched and tests galore —
Hooray for hops and bytes to store!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main changes: extraction of a PgSQL CommandComplete tag parser into a standalone function plus the addition of comprehensive unit tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v3.0-5499

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully extracts the PostgreSQL CommandComplete tag parser into a new, unit-testable function parse_pgsql_command_complete. The new implementation is well-structured and the accompanying unit tests are comprehensive, covering a wide range of commands and edge cases. I have provided a suggestion to improve performance by using std::string_view to avoid heap allocations during parsing. As a follow-up, consider replacing the original extract_pg_rows_affected function in PgSQLFFTO.cpp with a call to this new shared function to eliminate code duplication.

Comment on lines +12 to +14
#include <string>
#include <cctype>
#include <cstdlib>

Choose a reason for hiding this comment

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

medium

To improve performance and adopt modern C++ practices, it's better to use std::string_view and std::from_chars for parsing. This avoids heap allocations. Please update the includes accordingly. <string> and <cstdlib> will no longer be needed for the parsing logic if the next suggestion is applied.

Suggested change
#include <string>
#include <cctype>
#include <cstdlib>
#include <string_view>
#include <cctype>
#include <charconv>

Comment on lines +30 to +53
std::string tag(reinterpret_cast<const char*>(payload + begin), len - begin);

// Extract command type (first token)
size_t first_space = tag.find(' ');
if (first_space == std::string::npos) return result;

std::string command = tag.substr(0, first_space);

if (command == "SELECT" || command == "FETCH" || command == "MOVE") {
result.is_select = true;
} else if (command != "INSERT" && command != "UPDATE" &&
command != "DELETE" && command != "COPY" &&
command != "MERGE") {
return result; // Unknown command, no row count
}

// Extract row count (last token)
size_t last_space = tag.rfind(' ');
if (last_space == std::string::npos || last_space + 1 >= tag.size()) return result;

const char *rows_str = tag.c_str() + last_space + 1;
char *endptr = nullptr;
unsigned long long rows = std::strtoull(rows_str, &endptr, 10);
if (endptr == rows_str || *endptr != '\0') return result;

Choose a reason for hiding this comment

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

medium

This parsing logic can be optimized to avoid heap allocations from creating std::string objects. By using std::string_view, you can work with non-owning views of the payload. Additionally, std::from_chars is a more performant and safer alternative to strtoull for converting strings to numbers.

std::string_view tag(reinterpret_cast<const char*>(payload + begin), len - begin);

// Extract command type (first token)
size_t first_space = tag.find(' ');
if (first_space == std::string_view::npos) return result;

std::string_view command = tag.substr(0, first_space);

if (command == "SELECT" || command == "FETCH" || command == "MOVE") {
	result.is_select = true;
} else if (command != "INSERT" && command != "UPDATE" &&
		   command != "DELETE" && command != "COPY" &&
		   command != "MERGE") {
	return result;  // Unknown command, no row count
}

// Extract row count (last token)
size_t last_space = tag.rfind(' ');
if (last_space == std::string_view::npos || last_space + 1 >= tag.size()) return result;

std::string_view rows_sv = tag.substr(last_space + 1);
unsigned long long rows;
auto res = std::from_chars(rows_sv.data(), rows_sv.data() + rows_sv.size(), rows);
if (res.ec != std::errc() || res.ptr != rows_sv.data() + rows_sv.size()) return result;

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a standalone, unit-testable PostgreSQL CommandComplete tag parser to support FFTO unit testing goals from #5499.

Changes:

  • Added parse_pgsql_command_complete() + PgSQLCommandResult as a pure parser implementation (include/ + lib/).
  • Added a new unit test binary with 17 TAP assertions covering common commands and edge cases.
  • Wired the new unit test and library object into the corresponding Makefiles.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
include/PgSQLCommandComplete.h Declares the pure CommandComplete tag parser API and result struct.
lib/PgSQLCommandComplete.cpp Implements the parser logic (mirroring existing PgSQLFFTO parsing).
test/tap/tests/unit/pgsql_command_complete_unit-t.cpp Adds unit tests validating rows/is_select extraction for multiple tag formats.
test/tap/tests/unit/Makefile Registers the new unit test binary in UNIT_TESTS.
lib/Makefile Adds the new parser object file to libproxysql.a.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +5 to +6
* Logic mirrors extract_pg_rows_affected() in PgSQLFFTO.cpp.
*
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This new parser duplicates the existing CommandComplete parsing logic still implemented as extract_pg_rows_affected() in lib/PgSQLFFTO.cpp. That means the “extraction from PgSQLFFTO” described in the PR isn’t complete, and the two implementations can drift over time. Consider updating PgSQLFFTO to call parse_pgsql_command_complete() (or making extract_pg_rows_affected() a thin wrapper around it) and removing the duplicated parsing code so there is a single source of truth.

Copilot uses AI. Check for mistakes.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
lib/PgSQLCommandComplete.cpp (1)

50-55: Consider checking for strtoull overflow.

strtoull sets errno to ERANGE when the value overflows unsigned long long. While PostgreSQL row counts exceeding ULLONG_MAX are unlikely in practice, checking errno would make the parser more robust.

🛡️ Optional overflow check
+#include <cerrno>
+#include <climits>
 ...
 	const char *rows_str = tag.c_str() + last_space + 1;
 	char *endptr = nullptr;
+	errno = 0;
 	unsigned long long rows = std::strtoull(rows_str, &endptr, 10);
-	if (endptr == rows_str || *endptr != '\0') return result;
+	if (endptr == rows_str || *endptr != '\0' || errno == ERANGE) return result;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/PgSQLCommandComplete.cpp` around lines 50 - 55, Before calling
std::strtoull reset errno to 0, then call std::strtoull(rows_str, &endptr, 10)
and after the call check errno for ERANGE; if errno == ERANGE treat it as an
overflow (e.g., set rows to ULLONG_MAX or decide to skip setting result.rows)
instead of silently accepting a truncated value. Update the block that parses
rows_str (variables rows_str, endptr, rows) in PgSQLCommandComplete.cpp to
perform the errno reset/check and handle the ERANGE case consistently with
existing error handling.
test/tap/tests/unit/pgsql_command_complete_unit-t.cpp (1)

52-61: Consider adding is_select assertion for non-row-count commands.

The test_no_row_count() tests only verify rows == 0 but don't assert is_select == false for DDL commands like CREATE TABLE, DROP INDEX, and BEGIN. This would improve consistency with other test functions that check both fields.

🧪 Suggested enhancement
 static void test_no_row_count() {
 	auto r = parse("CREATE TABLE");
-	ok(r.rows == 0, "CREATE TABLE → rows=0 (no row count)");
+	ok(r.rows == 0 && r.is_select == false, "CREATE TABLE → rows=0, not select");
 
 	r = parse("DROP INDEX");
-	ok(r.rows == 0, "DROP INDEX → rows=0");
+	ok(r.rows == 0 && r.is_select == false, "DROP INDEX → rows=0, not select");
 
 	r = parse("BEGIN");
-	ok(r.rows == 0, "BEGIN → rows=0 (single token, no space)");
+	ok(r.rows == 0 && r.is_select == false, "BEGIN → rows=0, not select");
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/pgsql_command_complete_unit-t.cpp` around lines 52 - 61,
The test_no_row_count() unit test only asserts r.rows == 0 for parse("CREATE
TABLE"), parse("DROP INDEX"), and parse("BEGIN") but doesn't assert r.is_select
is false; update test_no_row_count() to also assert r.is_select == false for
each parsed result (the result variable r returned by parse()) so non-select
DDL/command cases are explicitly validated, keeping the existing rows assertions
and using the same ok(...) style assertions used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/PgSQLCommandComplete.cpp`:
- Around line 50-55: Before calling std::strtoull reset errno to 0, then call
std::strtoull(rows_str, &endptr, 10) and after the call check errno for ERANGE;
if errno == ERANGE treat it as an overflow (e.g., set rows to ULLONG_MAX or
decide to skip setting result.rows) instead of silently accepting a truncated
value. Update the block that parses rows_str (variables rows_str, endptr, rows)
in PgSQLCommandComplete.cpp to perform the errno reset/check and handle the
ERANGE case consistently with existing error handling.

In `@test/tap/tests/unit/pgsql_command_complete_unit-t.cpp`:
- Around line 52-61: The test_no_row_count() unit test only asserts r.rows == 0
for parse("CREATE TABLE"), parse("DROP INDEX"), and parse("BEGIN") but doesn't
assert r.is_select is false; update test_no_row_count() to also assert
r.is_select == false for each parsed result (the result variable r returned by
parse()) so non-select DDL/command cases are explicitly validated, keeping the
existing rows assertions and using the same ok(...) style assertions used
elsewhere.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 21c70c0d-b0cf-4b17-bc27-8a1921d4b1f8

📥 Commits

Reviewing files that changed from the base of the PR and between 1354b79 and f872beb.

📒 Files selected for processing (5)
  • include/PgSQLCommandComplete.h
  • lib/Makefile
  • lib/PgSQLCommandComplete.cpp
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/pgsql_command_complete_unit-t.cpp
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: CI-builds / builds (debian12,-dbg)
  • GitHub Check: CI-builds / builds (ubuntu22,-tap)
  • GitHub Check: Agent
  • GitHub Check: run / trigger
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-20T09:34:19.124Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:39-48
Timestamp: 2026-01-20T09:34:19.124Z
Learning: In ProxySQL's TAP test suite, resource leaks (e.g., not calling mysql_close() on early return paths) are commonly tolerated because test processes are short-lived and OS frees resources on exit. This pattern applies to all C++ test files under test/tap/tests. When reviewing, recognize this as a project-wide test convention and focus on test correctness and isolation rather than insisting on fixing such leaks in these test files.

Applied to files:

  • test/tap/tests/unit/pgsql_command_complete_unit-t.cpp
🪛 Clang (14.0.6)
test/tap/tests/unit/pgsql_command_complete_unit-t.cpp

[error] 11-11: 'tap.h' file not found

(clang-diagnostic-error)

include/PgSQLCommandComplete.h

[error] 14-14: 'cstdint' file not found

(clang-diagnostic-error)

🪛 Cppcheck (2.20.0)
test/tap/tests/unit/pgsql_command_complete_unit-t.cpp

[warning] 86-86: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)


[warning] 31-31: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)

🔇 Additional comments (6)
test/tap/tests/unit/Makefile (1)

237-237: LGTM!

The new unit test target follows the established naming convention and is properly integrated with the existing build infrastructure.

lib/Makefile (1)

111-111: LGTM!

The new object file is properly added to the build and follows the existing pattern for PostgreSQL-related modules.

test/tap/tests/unit/pgsql_command_complete_unit-t.cpp (2)

19-22: LGTM!

The helper function is well-designed for the test context. The direct call to parse_pgsql_command_complete() at line 65 for the nullptr test case correctly bypasses this helper to avoid undefined behavior from strlen(nullptr).


84-96: LGTM!

Test structure is clean with proper plan(), initialization, categorized test functions, cleanup, and exit status. The test count (17) correctly matches the sum of all assertions.

include/PgSQLCommandComplete.h (1)

1-48: LGTM!

Well-structured header with clear documentation, appropriate include guards, and a clean pure-function API. The use of uint64_t for row counts handles PostgreSQL's potential for very large result sets.

lib/PgSQLCommandComplete.cpp (1)

16-57: LGTM!

The implementation is clean and handles edge cases well:

  • Null/empty input handling
  • Whitespace trimming (leading and trailing)
  • Command classification for SELECT-like vs DML
  • Row count extraction from last token with proper validation

New: include/MySQLProtocolUtils.h, lib/MySQLProtocolUtils.cpp

Functions:
- mysql_read_lenenc_int(): reads MySQL length-encoded integers from
  raw buffers (mirrors read_lenenc_int in MySQLFFTO.cpp)
- mysql_build_packet(): constructs MySQL wire-format packets (3-byte
  length + seq_id + payload) for crafting test data
36 test cases covering both MySQL and PgSQL FFTO protocol parsing:

MySQL length-encoded integers (11 tests):
- 1-byte, 2-byte, 3-byte, 8-byte values
- Truncated buffers, empty input

MySQL packet building (9 tests):
- Normal, large (1000 bytes), empty packets
- Header validation (length, seq_id, payload integrity)

MySQL OK packet parsing (2 tests):
- affected_rows extraction (1-byte and 2-byte lenenc)

PgSQL CommandComplete extended (7 tests):
- INSERT with OID, large row count, zero rows
- All 8 command types in one sweep, 9 DDL commands
- Null-terminated payload (real wire format)

Fragmentation simulation (6 tests):
- Truncated lenenc (partial data)
- Multi-packet stream building and header verification
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
test/tap/tests/unit/ffto_protocol_unit-t.cpp (1)

223-224: Use exact-value assertion for fragmented lenenc decode.

ok(val > 0, ...) is too permissive; assert the exact decoded value to catch subtle decode regressions.

✅ Stronger assertion
 	p = full; len = 4;
 	val = mysql_read_lenenc_int(p, len);
-	ok(val > 0, "fragmented: full 3-byte lenenc decoded successfully");
+	ok(val == 1000000, "fragmented: full 3-byte lenenc decoded as 1,000,000");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/tap/tests/unit/ffto_protocol_unit-t.cpp` around lines 223 - 224, Replace
the permissive positive check with an exact-value assertion: when calling
mysql_read_lenenc_int(p, len) store the result in val and assert it equals the
expected decoded length (use the test framework's equality assertion) instead of
ok(val > 0,...). Update the test around mysql_read_lenenc_int, val and len to
compare val to the known expected 3-byte length value to catch regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@lib/MySQLProtocolUtils.cpp`:
- Around line 11-41: The function mysql_read_lenenc_int currently advances the
cursor (buf/len) immediately after reading the prefix byte and then returns 0 on
insufficient trailing bytes, making truncation indistinguishable from a
legitimate zero and leaving the cursor advanced; fix this by validating that the
buffer has the required trailing bytes before mutating buf/len (or if you must
read the prefix first, restore buf/len to their original values on any early
return), and only advance buf and decrement len after you have confirmed enough
bytes for the selected length-encoded branch (ensure this change is applied in
mysql_read_lenenc_int for the 0xFC, 0xFD and 0xFE branches).
- Around line 50-59: The mysql_build_packet function currently writes only the
lower 24 bits of payload_len into the 3-byte header and blindly memcpy's
payload, which corrupts packets for payload_len > 0xFFFFFF and risks
dereferencing a null payload; add guard clauses at the start of
mysql_build_packet to (1) validate payload_len is <= 0xFFFFFF and reject/return
an error code (e.g., -1) if it is larger, and (2) ensure that if payload_len > 0
the payload pointer is non-null and otherwise return an error; do not perform
the header write or memcpy when the checks fail so out_buf, seq_id, and memcpy
are only used after validation.

---

Nitpick comments:
In `@test/tap/tests/unit/ffto_protocol_unit-t.cpp`:
- Around line 223-224: Replace the permissive positive check with an exact-value
assertion: when calling mysql_read_lenenc_int(p, len) store the result in val
and assert it equals the expected decoded length (use the test framework's
equality assertion) instead of ok(val > 0,...). Update the test around
mysql_read_lenenc_int, val and len to compare val to the known expected 3-byte
length value to catch regressions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 83c88556-3ba1-4187-b833-ae29b63ba514

📥 Commits

Reviewing files that changed from the base of the PR and between f872beb and 1274fb7.

📒 Files selected for processing (5)
  • include/MySQLProtocolUtils.h
  • lib/Makefile
  • lib/MySQLProtocolUtils.cpp
  • test/tap/tests/unit/Makefile
  • test/tap/tests/unit/ffto_protocol_unit-t.cpp
✅ Files skipped from review due to trivial changes (2)
  • lib/Makefile
  • test/tap/tests/unit/Makefile
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: CI-builds / builds (debian12,-dbg)
  • GitHub Check: CI-builds / builds (ubuntu22,-tap)
  • GitHub Check: run / trigger
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2026-01-20T09:34:19.124Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:39-48
Timestamp: 2026-01-20T09:34:19.124Z
Learning: In ProxySQL's TAP test suite, resource leaks (e.g., not calling mysql_close() on early return paths) are commonly tolerated because test processes are short-lived and OS frees resources on exit. This pattern applies to all C++ test files under test/tap/tests. When reviewing, recognize this as a project-wide test convention and focus on test correctness and isolation rather than insisting on fixing such leaks in these test files.

Applied to files:

  • test/tap/tests/unit/ffto_protocol_unit-t.cpp
📚 Learning: 2026-01-20T07:40:34.938Z
Learnt from: yuji-hatakeyama
Repo: sysown/proxysql PR: 5307
File: test/tap/tests/reg_test_5306-show_warnings_with_comment-t.cpp:24-28
Timestamp: 2026-01-20T07:40:34.938Z
Learning: In ProxySQL test files, calling `mysql_error(NULL)` after `mysql_init()` failure is safe because the MariaDB client library implementation returns an empty string for NULL handles (not undefined behavior).

Applied to files:

  • test/tap/tests/unit/ffto_protocol_unit-t.cpp
🪛 Clang (14.0.6)
include/MySQLProtocolUtils.h

[error] 14-14: 'cstdint' file not found

(clang-diagnostic-error)

test/tap/tests/unit/ffto_protocol_unit-t.cpp

[error] 13-13: 'tap.h' file not found

(clang-diagnostic-error)

🪛 Cppcheck (2.20.0)
test/tap/tests/unit/ffto_protocol_unit-t.cpp

[warning] 86-86: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)


[warning] 31-31: If memory allocation fails, then there is a possible null pointer dereference

(nullPointerOutOfMemory)

Comment on lines +11 to +41
uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
if (len == 0) return 0;
uint8_t first_byte = buf[0];
buf++; len--;
if (first_byte < 0xFB) return first_byte;
if (first_byte == 0xFC) {
if (len < 2) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
buf += 2; len -= 2;
return value;
}
if (first_byte == 0xFD) {
if (len < 3) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16);
buf += 3; len -= 3;
return value;
}
if (first_byte == 0xFE) {
if (len < 8) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16)
| (static_cast<uint64_t>(buf[3]) << 24)
| (static_cast<uint64_t>(buf[4]) << 32)
| (static_cast<uint64_t>(buf[5]) << 40)
| (static_cast<uint64_t>(buf[6]) << 48)
| (static_cast<uint64_t>(buf[7]) << 56);
buf += 8; len -= 8;
return value;
}
return 0;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, locate and examine the MySQLProtocolUtils.cpp file
fd -type f -name "MySQLProtocolUtils.cpp" | head -20

Repository: sysown/proxysql

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Get file size and read the function
if [ -f "lib/MySQLProtocolUtils.cpp" ]; then
  wc -l "lib/MySQLProtocolUtils.cpp"
  echo "---"
  head -50 "lib/MySQLProtocolUtils.cpp"
fi

Repository: sysown/proxysql

Length of output: 1434


🏁 Script executed:

#!/bin/bash
# Search for all calls to mysql_read_lenenc_int to understand usage patterns
rg "mysql_read_lenenc_int" -A 3 -B 1

Repository: sysown/proxysql

Length of output: 5679


🏁 Script executed:

#!/bin/bash
# Check if there are any retry or partial buffer handling patterns
rg "partial\|retry\|buffer.*fail\|truncat" -t cpp -i | head -40

Repository: sysown/proxysql

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for MySQL_Protocol.cpp and check the specific lines mentioned
fd -type f -name "MySQL_Protocol.cpp" | head -5

Repository: sysown/proxysql

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Look for broader usage context - search for patterns where mysql_read_lenenc_int is called
# to understand if callers manage cursor state or expect rollback
rg -A 5 -B 5 "mysql_read_lenenc_int" lib/ include/ --type cpp | head -100

Repository: sysown/proxysql

Length of output: 1366


🏁 Script executed:

#!/bin/bash
# Check if there are any error handling patterns or decode status checks
# around lenenc parsing that might indicate retry expectations
rg "decode.*status\|malformed\|truncat" lib/ --type cpp -i | head -30

Repository: sysown/proxysql

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Read the full header file to see documentation and understand expected behavior
cat -n "include/MySQLProtocolUtils.h" | head -50

Repository: sysown/proxysql

Length of output: 1898


🏁 Script executed:

#!/bin/bash
# Search for MySQL_Protocol.cpp mentioned in the review comment scratchpad
fd -type f -name "*Protocol*" | grep -i mysql

Repository: sysown/proxysql

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Look for actual parsing code that calls mysql_read_lenenc_int
# Search broader for protocol parsing and decode patterns
rg "mysql_read_lenenc_int" -B 10 -A 10

Repository: sysown/proxysql

Length of output: 12681


🏁 Script executed:

#!/bin/bash
# Check for packet parsing and error handling contexts
rg "packet.*decode\|parse.*lenenc\|handle.*truncat" -i | head -40

Repository: sysown/proxysql

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Look at the fragmented test more carefully to understand retry expectations
sed -n '200,250p' "test/tap/tests/unit/ffto_protocol_unit-t.cpp"

Repository: sysown/proxysql

Length of output: 2088


🏁 Script executed:

#!/bin/bash
# Search for any actual retry or partial buffer handling patterns in the codebase
rg "while.*len|retry|partial.*buffer" | head -30

Repository: sysown/proxysql

Length of output: 2160


🏁 Script executed:

#!/bin/bash
# Check if there's actual usage in protocol parsing that demonstrates the issue
rg "mysql_read_lenenc_int" -B 15 -A 15 | grep -A 20 -B 20 "while\|retry\|if.*len\|buffer"

Repository: sysown/proxysql

Length of output: 7466


Cursor advances before validating trailing bytes, creating ambiguous return and unsafe retry semantics.

The function consumes the prefix byte and advances buf/len before checking if the required trailing bytes are available. It then returns 0 for both valid-zero values and truncation failures, making them indistinguishable. This prevents callers from safely retrying on partial buffer conditions without external cursor management.

💡 Minimal fix: rollback cursor on failure
 uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
-	if (len == 0) return 0;
-	uint8_t first_byte = buf[0];
-	buf++; len--;
+	if (buf == nullptr || len == 0) return 0;
+	const unsigned char* orig_buf = buf;
+	size_t orig_len = len;
+	uint8_t first_byte = buf[0];
+	buf++; len--;
+	auto fail = [&]() -> uint64_t {
+		buf = orig_buf;
+		len = orig_len;
+		return 0;
+	};
 	if (first_byte < 0xFB) return first_byte;
 	if (first_byte == 0xFC) {
-		if (len < 2) return 0;
+		if (len < 2) return fail();
 		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
 		buf += 2; len -= 2;
 		return value;
 	}
 	if (first_byte == 0xFD) {
-		if (len < 3) return 0;
+		if (len < 3) return fail();
 		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
 			| (static_cast<uint64_t>(buf[2]) << 16);
 		buf += 3; len -= 3;
 		return value;
 	}
 	if (first_byte == 0xFE) {
-		if (len < 8) return 0;
+		if (len < 8) return fail();
 		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
 			| (static_cast<uint64_t>(buf[2]) << 16)
 			| (static_cast<uint64_t>(buf[3]) << 24)
 			| (static_cast<uint64_t>(buf[4]) << 32)
 			| (static_cast<uint64_t>(buf[5]) << 40)
 			| (static_cast<uint64_t>(buf[6]) << 48)
 			| (static_cast<uint64_t>(buf[7]) << 56);
 		buf += 8; len -= 8;
 		return value;
 	}
-	return 0;
+	return fail();
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
if (len == 0) return 0;
uint8_t first_byte = buf[0];
buf++; len--;
if (first_byte < 0xFB) return first_byte;
if (first_byte == 0xFC) {
if (len < 2) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
buf += 2; len -= 2;
return value;
}
if (first_byte == 0xFD) {
if (len < 3) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16);
buf += 3; len -= 3;
return value;
}
if (first_byte == 0xFE) {
if (len < 8) return 0;
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16)
| (static_cast<uint64_t>(buf[3]) << 24)
| (static_cast<uint64_t>(buf[4]) << 32)
| (static_cast<uint64_t>(buf[5]) << 40)
| (static_cast<uint64_t>(buf[6]) << 48)
| (static_cast<uint64_t>(buf[7]) << 56);
buf += 8; len -= 8;
return value;
}
return 0;
uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
if (buf == nullptr || len == 0) return 0;
const unsigned char* orig_buf = buf;
size_t orig_len = len;
uint8_t first_byte = buf[0];
buf++; len--;
auto fail = [&]() -> uint64_t {
buf = orig_buf;
len = orig_len;
return 0;
};
if (first_byte < 0xFB) return first_byte;
if (first_byte == 0xFC) {
if (len < 2) return fail();
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
buf += 2; len -= 2;
return value;
}
if (first_byte == 0xFD) {
if (len < 3) return fail();
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16);
buf += 3; len -= 3;
return value;
}
if (first_byte == 0xFE) {
if (len < 8) return fail();
uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
| (static_cast<uint64_t>(buf[2]) << 16)
| (static_cast<uint64_t>(buf[3]) << 24)
| (static_cast<uint64_t>(buf[4]) << 32)
| (static_cast<uint64_t>(buf[5]) << 40)
| (static_cast<uint64_t>(buf[6]) << 48)
| (static_cast<uint64_t>(buf[7]) << 56);
buf += 8; len -= 8;
return value;
}
return fail();
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQLProtocolUtils.cpp` around lines 11 - 41, The function
mysql_read_lenenc_int currently advances the cursor (buf/len) immediately after
reading the prefix byte and then returns 0 on insufficient trailing bytes,
making truncation indistinguishable from a legitimate zero and leaving the
cursor advanced; fix this by validating that the buffer has the required
trailing bytes before mutating buf/len (or if you must read the prefix first,
restore buf/len to their original values on any early return), and only advance
buf and decrement len after you have confirmed enough bytes for the selected
length-encoded branch (ensure this change is applied in mysql_read_lenenc_int
for the 0xFC, 0xFD and 0xFE branches).

Comment on lines +50 to +59
// 3-byte length (little-endian) + 1-byte sequence
out_buf[0] = payload_len & 0xFF;
out_buf[1] = (payload_len >> 8) & 0xFF;
out_buf[2] = (payload_len >> 16) & 0xFF;
out_buf[3] = seq_id;
if (payload && payload_len > 0) {
memcpy(out_buf + 4, payload, payload_len);
}
return payload_len + 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 | 🟠 Major

❓ Verification inconclusive

Script executed:

find . -name "MySQLProtocolUtils.cpp" -type f

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

./lib/MySQLProtocolUtils.cpp

Script executed:

git ls-files | grep -i mysql

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

.github/workflows/CI-3p-aiomysql.yml
.github/workflows/CI-3p-mysql-connector-j.yml
.github/workflows/CI-3p-php-pdo-mysql.yml
RAG_POC/sample_mysql.sql
deps/mariadb-client-library/mysql.h.patch
doc/internal/MySQL_Connection.md
doc/internal/MySQL_Logging_Prepared_Statements_Parameters.md
doc/internal/MySQL_Session.md
docker/1backend-complex-config/mysql/Dockerfile
docker/1backend-complex-config/mysql/import_schema.sh
docker/1backend-complex-config/mysql/schema.sql
docker/images/mysql/mysql-empty/Dockerfile
docker/images/mysql/mysql-simple-dump/Dockerfile
docker/images/mysql/mysql-simple-dump/import_schema.sh
docker/images/mysql/mysql-simple-dump/schema.sql
docker/scenarios/repl1/mysql.sh
docker/scenarios/repl1/mysql_add.cnf_
include/MySQLFFTO.hpp
include/MySQLProtocolUtils.h
include/MySQL_Authentication.hpp
include/MySQL_Catalog.h
include/MySQL_Data_Stream.h
include/MySQL_FTS.h
include/MySQL_HostGroups_Manager.h
include/MySQL_LDAP_Authentication.hpp
include/MySQL_Logger.hpp
include/MySQL_Monitor.hpp
include/MySQL_PreparedStatement.h
include/MySQL_Prepared_Stmt_info.h
include/MySQL_Protocol.h
include/MySQL_Query_Cache.h
include/MySQL_Query_Processor.h
include/MySQL_Session.h
include/MySQL_Set_Stmt_Parser.h
include/MySQL_Thread.h
include/MySQL_Tool_Handler.h
include/MySQL_Variables.h
include/MySQL_encode.h
include/mysql_backend.h
include/mysql_connection.h
lib/MySQLFFTO.cpp
lib/MySQLProtocolUtils.cpp
lib/MySQL_Authentication.cpp
lib/MySQL_Catalog.cpp
lib/MySQL_FTS.cpp
lib/MySQL_HostGroups_Manager.cpp
lib/MySQL_Logger.cpp
lib/MySQL_Monitor.cpp
lib/MySQL_PreparedStatement.cpp
lib/MySQL_Protocol.cpp
lib/MySQL_Query_Cache.cpp
lib/MySQL_Query_Processor.cpp
lib/MySQL_ResultSet.cpp
lib/MySQL_Session.cpp
lib/MySQL_Set_Stmt_Parser.cpp
lib/MySQL_Thread.cpp
lib/MySQL_Tool_Handler.cpp
lib/MySQL_Variables.cpp
lib/MySQL_encode.cpp
lib/mysql_backend.cpp
lib/mysql_connection.cpp
lib/mysql_data_stream.cpp
scripts/add_mysql_user.sh
scripts/copy_stackexchange_Posts_mysql_to_sqlite3.py
test-scripts/deps/mysqlbinlog
test/afl_digest_test/afl_mysql_query_digest.cpp
test/deps/mysql-8.4.0-clang17.patch
test/deps/mysql-connector-c-8.4.0/mysql-connector-c
test/deps/mysql-connector-c/mysql-connector-c
test/infra/infra-mariadb10/bin/docker-mysql-post.bash
test/infra/infra-mariadb10/conf/mysql/addition_to_sys.sql
test/infra/infra-mariadb10/conf/mysql/mysql1/my.cnf
test/infra/infra-mariadb10/conf/mysql/mysql2/my.cnf
test/infra/infra-mariadb10/conf/mysql/mysql3/my.cnf
test/infra/infra-mysql57-binlog/.env
test/infra/infra-mysql57-binlog/bin/docker-mysql-post.bash
test/infra/infra-mysql57-binlog/bin/docker-orchestrator-post.bash
test/infra/infra-mysql57-binlog/bin/docker-proxy-post.bash
test/infra/infra-mysql57-binlog/conf/mysql/mysql1/my.cnf
test/infra/infra-mysql57-binlog/conf/mysql/mysql2/my.cnf
test/infra/infra-mysql57-binlog/conf/mysql/mysql3/my.cnf
test/infra/infra-mysql57-binlog/conf/orchestrator/orc1/orchestrator.conf.json
test/infra/infra-mysql57-binlog/conf/orchestrator/orc2/orchestrator.conf.json
test/infra/infra-mysql57-binlog/conf/orchestrator/orc3/orchestrator.conf.json
test/infra/infra-mysql57-binlog/conf/orchestrator/remove-proxysql-host.bash
test/infra/infra-mysql57-binlog/conf/proxysql/infra-config.sql
test/infra/infra-mysql57-binlog/docker-compose-destroy.bash
test/infra/infra-mysql57-binlog/docker-compose-init.bash
test/infra/infra-mysql57-binlog/docker-compose.yml
test/infra/infra-mysql57/.env
test/infra/infra-mysql57/bin/docker-mysql-post.bash
test/infra/infra-mysql57/bin/docker-orchestrator-post.bash
test/infra/infra-mysql57/bin/docker-proxy-post.bash
test/infra/infra-mysql57/bin/local-docker-benchmark.bash
test/infra/infra-mysql57/conf/mysql/mysql1/my.cnf
test/infra/infra-mysql57/conf/mysql/mysql2/my.cnf
test/infra/infra-mysql57/conf/mysql/mysql3/my.cnf
test/infra/infra-mysql57/conf/mysql/ssl/configure_ssl.sh
test/infra/infra-mysql57/conf/orchestrator/orc1/orchestrator.conf.json
test/infra/infra-mysql57/conf/orchestrator/orc2/orchestrator.conf.json
test/infra/infra-mysql57/conf/orchestrator/orc3/orchestrator.conf.json
test/infra/infra-mysql57/conf/orchestrator/remove-proxysql-host.bash
test/infra/infra-mysql57/conf/proxysql/infra-config.sql
test/infra/infra-mysql57/docker-compose-destroy.bash
test/infra/infra-mysql57/docker-compose-init.bash
test/infra/infra-mysql57/docker-compose.yml
test/infra/infra-mysql84/.env
test/infra/infra-mysql84/bin/docker-mysql-post.bash
test/infra/infra-mysql84/bin/docker-orchestrator-post.bash
test/infra/infra-mysql84/bin/docker-proxy-post.bash
test/infra/infra-mysql84/bin/local-docker-benchmark.bash
test/infra/infra-mysql84/conf/mysql/mysql1/my.cnf
test/infra/infra-mysql84/conf/mysql/mysql2/my.cnf
test/infra/infra-mysql84/conf/mysql/mysql3/my.cnf
test/infra/infra-mysql84/conf/mysql/ssl/configure_ssl.sh
test/infra/infra-mysql84/conf/orchestrator/orc1/orchestrator.conf.json
test/infra/infra-mysql84/conf/orchestrator/orc2/orchestrator.conf.json
test/infra/infra-mysql84/conf/orchestrator/orc3/orchestrator.conf.json
test/infra/infra-mysql84/conf/orchestrator/remove-proxysql-host.bash
test/infra/infra-mysql84/conf/proxysql/infra-config.sql
test/infra/infra-mysql84/docker-compose-destroy.bash
test/infra/infra-mysql84/docker-compose-init.bash
test/infra/infra-mysql84/docker-compose.yml
test/scripts/deps/mysqlbinlog
test/tap/groups/ai/mysql-seed.sql
test/tap/groups/mysql-auto_increment_delay_multiplex=0/pre-proxysql.sql
test/tap/groups/mysql-multiplexing=false/pre-proxysql.sql
test/tap/groups/mysql-query_digests=0/pre-proxysql.sql
test/tap/groups/mysql-query_digests_keep_comment=1/pre-proxysql.sql
test/tap/groups/mysql84-gr/infras.lst
test/tap/groups/mysql84-gr/pre-proxysql.bash
test/tap/groups/mysql84/env.sh
test/tap/groups/mysql84/infras.lst
test/tap/groups/mysql84/pre-proxysql.bash
test/tap/groups/mysql90-gr/infras.lst
test/tap/groups/mysql90-gr/pre-proxysql.bash
test/tap/groups/mysql90/infras.lst
test/tap/groups/mysql90/pre-proxysql.bash
test/tap/groups/mysql91-gr/infras.lst
test/tap/groups/mysql91-gr/pre-proxysql.bash
test/tap/groups/mysql91/infras.lst
test/tap/groups/mysql91/pre-proxysql.bash
test/tap/groups/mysql92-gr/infras.lst
test/tap/groups/mysql92-gr/pre-proxysql.bash
test/tap/groups/mysql92/infras.lst
test/tap/groups/mysql92/pre-proxysql.bash
test/tap/groups/mysql93-gr/infras.lst
test/tap/groups/mysql93-gr/pre-proxysql.bash
test/tap/groups/mysql93/infras.lst
test/tap/groups/mysql93/pre-proxysql.bash
test/tap/tests/mcp_mixed_mysql_pgsql_concurrency_stress-t.cpp
test/tap/tests/mcp_mysql_concurrency_stress-t.cpp
test/tap/tests/mysql-fast_forward-t.cpp
test/tap/tests/mysql-init_connect-1-t.cpp
test/tap/tests/mysql-init_connect-2-t.cpp
test/tap/tests/mysql-last_insert_id-t.cpp
test/tap/tests/mysql-mirror1-t.cpp
test/tap/tests/mysql-protocol_compression_level-t.cpp
test/tap/tests/mysql-reg_test_4707_threshold_resultset_size-t.cpp
test/tap/tests/mysql-reg_test_4716_single_semicolon-t.cpp
test/tap/tests/mysql-reg_test_4723_query_cache_stores_empty_result-t.cpp
test/tap/tests/mysql-reg_test_4867_query_rules-t.cpp
test/tap/tests/mysql-select_version_without_backend-t.cpp
test/tap/tests/mysql-set_transaction-t.cpp
test/tap/tests/mysql-set_wait_timeout-t.cpp
test/tap/tests/mysql-show_ssl_version-t.cpp
test/tap/tests/mysql-sql_log_bin-error-t.cpp
test/tap/tests/mysql-test_malformed_packet-t.cpp
test/tap/tests/mysql-test_ssl_CA-t.cpp
test/tap/tests/mysql-watchdog_test-t.cpp
test/tap/tests/mysql_hostgroup_attributes-servers_defaults-t.cpp
test/tap/tests/mysql_hostgroup_attributes_config_file-t.cpp
test/tap/tests/mysql_query_logging_memory-t.cpp
test/tap/tests/mysql_reconnect.cpp
test/tap/tests/mysql_stmt_send_long_data-t.cpp
test/tap/tests/mysql_stmt_send_long_data_large-t.cpp
test/tap/tests/reg_test_3606-mysql_warnings-t.cpp
test/tap/tests/reg_test_3992_fast_forward_malformed_packet-mysqlsh-t.cpp
test/tap/tests/reg_test_3992_fast_forward_malformed_packet-pymysql-t.py
test/tap/tests/reg_test_4399-stats_mysql_query_digest-t.cpp
test/tap/tests/reg_test_4402-mysql_fields-t.cpp
test/tap/tests/repro_3404-mysql_close_fd_leak.cpp
test/tap/tests/test_cluster_sync_mysql_servers-t.cpp
test/tap/tests/test_ffto_mysql-t.cpp
test/tap/tests/test_ldap_stats_mysql_users.cpp
test/tap/tests/test_mysql_connect_retries-t.cpp
test/tap/tests/test_mysql_connect_retries_delay-t.cpp
test/tap/tests/test_mysql_hostgroup_attributes-1-t.cpp
test/tap/tests/test_mysql_query_digests_stages-t.cpp
test/tap/tests/test_mysql_query_rules_fast_routing-t.cpp
test/tap/tests/test_mysqlsh-t.cpp

Script executed:

git ls-files lib/ | head -20

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

lib/AI_Features_Manager.cpp
lib/AI_Tool_Handler.cpp
lib/AI_Vector_Storage.cpp
lib/Admin_Bootstrap.cpp
lib/Admin_FlushVariables.cpp
lib/Admin_Handler.cpp
lib/Admin_Tool_Handler.cpp
lib/Anomaly_Detector.cpp
lib/BaseHGC.cpp
lib/BaseSrvList.cpp
lib/Base_HostGroups_Manager.cpp
lib/Base_Session.cpp
lib/Base_Thread.cpp
lib/Cache_Tool_Handler.cpp
lib/Chart_bundle_js.cpp
lib/ClickHouse_Authentication.cpp
lib/ClickHouse_Server.cpp
lib/Config_Tool_Handler.cpp
lib/Discovery_Schema.cpp
lib/GTID_Server_Data.cpp

Script executed:

git ls-files | grep -i protocol

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

doc/PROMETHEUS_PROTOCOL_LABELS.md
include/MySQLProtocolUtils.h
include/MySQL_Protocol.h
include/PgSQL_Protocol.h
include/proxy_protocol_info.h
lib/MySQLProtocolUtils.cpp
lib/MySQL_Protocol.cpp
lib/PgSQL_Protocol.cpp
lib/proxy_protocol_info.cpp
test/tap/tests/mysql-protocol_compression_level-t.cpp
test/tap/tests/pgsql-extended_query_protocol_query_rules_test-t.cpp
test/tap/tests/pgsql-extended_query_protocol_test-t.cpp
test/tap/tests/test_PROXY_Protocol-t.cpp
test/tap/tests/unit/ffto_protocol_unit-t.cpp
test/tap/tests/unit/protocol_unit-t.cpp

Script executed:

git ls-files lib/

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

lib/AI_Features_Manager.cpp
lib/AI_Tool_Handler.cpp
lib/AI_Vector_Storage.cpp
lib/Admin_Bootstrap.cpp
lib/Admin_FlushVariables.cpp
lib/Admin_Handler.cpp
lib/Admin_Tool_Handler.cpp
lib/Anomaly_Detector.cpp
lib/BaseHGC.cpp
lib/BaseSrvList.cpp
lib/Base_HostGroups_Manager.cpp
lib/Base_Session.cpp
lib/Base_Thread.cpp
lib/Cache_Tool_Handler.cpp
lib/Chart_bundle_js.cpp
lib/ClickHouse_Authentication.cpp
lib/ClickHouse_Server.cpp
lib/Config_Tool_Handler.cpp
lib/Discovery_Schema.cpp
lib/GTID_Server_Data.cpp
lib/GenAI_Thread.cpp
lib/HostgroupRouting.cpp
lib/LLM_Bridge.cpp
lib/LLM_Clients.cpp
lib/MCP_Endpoint.cpp
lib/MCP_Thread.cpp
lib/MCP_Tool_Handler.cpp
lib/Makefile
lib/MonitorHealthDecision.cpp
lib/MyHGC.cpp
lib/MySQLFFTO.cpp
lib/MySQLProtocolUtils.cpp
lib/MySQL_Authentication.cpp
lib/MySQL_Catalog.cpp
lib/MySQL_FTS.cpp
lib/MySQL_HostGroups_Manager.cpp
lib/MySQL_Logger.cpp
lib/MySQL_Monitor.cpp
lib/MySQL_PreparedStatement.cpp
lib/MySQL_Protocol.cpp
lib/MySQL_Query_Cache.cpp
lib/MySQL_Query_Processor.cpp
lib/MySQL_ResultSet.cpp
lib/MySQL_Session.cpp
lib/MySQL_Set_Stmt_Parser.cpp
lib/MySQL_Thread.cpp
lib/MySQL_Tool_Handler.cpp
lib/MySQL_Variables.cpp
lib/MySQL_encode.cpp
lib/MySrvC.cpp
lib/MySrvConnList.cpp
lib/Observe_Tool_Handler.cpp
lib/PgSQLCommandComplete.cpp
lib/PgSQLFFTO.cpp
lib/PgSQL_Authentication.cpp
lib/PgSQL_Backend.cpp
lib/PgSQL_Connection.cpp
lib/PgSQL_Data_Stream.cpp
lib/PgSQL_Error_Helper.cpp
lib/PgSQL_ExplicitTxnStateMgr.cpp
lib/PgSQL_Extended_Query_Message.cpp
lib/PgSQL_HostGroups_Manager.cpp
lib/PgSQL_Logger.cpp
lib/PgSQL_Monitor.cpp
lib/PgSQL_PreparedStatement.cpp
lib/PgSQL_Protocol.cpp
lib/PgSQL_Query_Cache.cpp
lib/PgSQL_Query_Processor.cpp
lib/PgSQL_Session.cpp
lib/PgSQL_Set_Stmt_Parser.cpp
lib/PgSQL_Static_Harvester.cpp
lib/PgSQL_Thread.cpp
lib/PgSQL_Variables.cpp
lib/PgSQL_Variables_Validator.cpp
lib/ProxySQL_Admin.cpp
lib/ProxySQL_Admin_Disk_Upgrade.cpp
lib/ProxySQL_Admin_Scheduler.cpp
lib/ProxySQL_Admin_Stats.cpp
lib/ProxySQL_Admin_Tests.cpp
lib/ProxySQL_Admin_Tests2.cpp
lib/ProxySQL_Cluster.cpp
lib/ProxySQL_Config.cpp
lib/ProxySQL_GloVars.cpp
lib/ProxySQL_HTTP_Server.cpp
lib/ProxySQL_MCP_Server.cpp
lib/ProxySQL_Poll.cpp
lib/ProxySQL_RESTAPI_Server.cpp
lib/ProxySQL_Restapi.cpp
lib/ProxySQL_Statistics.cpp
lib/QP_query_digest_stats.cpp
lib/QP_rule_text.cpp
lib/Query_Cache.cpp
lib/Query_Processor.cpp
lib/Query_Tool_Handler.cpp
lib/RAG_Tool_Handler.cpp
lib/SHA-crypt.txt
lib/SpookyV2.cpp
lib/Static_Harvester.cpp
lib/Stats_Tool_Handler.cpp
lib/TSDB_Dashboard_html.cpp
lib/TransactionState.cpp
lib/c_tokenizer.cpp
lib/c_tokenizer_legacy.cpp
lib/configfile.cpp
lib/debug.cpp
lib/font-awesome.min.css.cpp
lib/gen_utils.cpp
lib/log_utils.cpp
lib/main-bundle.min.css.cpp
lib/mysql_backend.cpp
lib/mysql_connection.cpp
lib/mysql_data_stream.cpp
lib/network.cpp
lib/pgsql_tokenizer.cpp
lib/proxy_protocol_info.cpp
lib/proxy_sqlite3_symbols.cpp
lib/proxysql_coredump.cpp
lib/proxysql_find_charset.cpp
lib/proxysql_sslkeylog.cpp
lib/proxysql_utils.cpp
lib/sha256crypt.cpp
lib/sha256crypt.orig.c
lib/sqlite3db.cpp
lib/thread.cpp

Script executed:

rg "mysql_build_packet" --type cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

test/tap/tests/unit/ffto_protocol_unit-t.cpp:static void test_mysql_build_packet() {
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload, 9, 0, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(nullptr, 0, 1, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	mysql_build_packet(ok_payload, 3, 1, pkt);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p1, 4, 0, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p2, 3, 1, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p3, 5, 2, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	test_mysql_build_packet();         // 4
lib/MySQLProtocolUtils.cpp:size_t mysql_build_packet(
include/MySQLProtocolUtils.h:size_t mysql_build_packet(

Script executed:

rg "mysql_build_packet"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

test/tap/tests/unit/ffto_protocol_unit-t.cpp:static void test_mysql_build_packet() {
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload, 9, 0, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(nullptr, 0, 1, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	mysql_build_packet(ok_payload, 3, 1, pkt);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p1, 4, 0, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p2, 3, 1, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p3, 5, 2, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	test_mysql_build_packet();         // 4
lib/MySQLProtocolUtils.cpp:size_t mysql_build_packet(
include/MySQLProtocolUtils.h:size_t mysql_build_packet(

Script executed:

ls -la

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

total 264
drwxr-xr-x 23 jailuser jailuser  1020 Mar 22 11:20 .
drwxr-xr-x  1 jailuser jailuser   180 Mar 22 11:20 ..
-rw-r--r--  1 jailuser jailuser 13311 Mar 22 11:20 .aider.conf.yml
-rw-r--r--  1 root     root       777 Mar 22 11:20 .clang-tidy
drwxr-xr-x  6 jailuser jailuser   240 Mar 22 11:25 .git
drwxr-xr-x  4 jailuser jailuser   100 Mar 22 11:20 .github
-rw-r--r--  1 jailuser jailuser  3721 Mar 22 11:20 .gitignore
-rw-r--r--  1 jailuser jailuser  1834 Mar 22 11:20 Aurora_Automated_Testing.md
-rw-r--r--  1 jailuser jailuser 16444 Mar 22 11:20 CHANGELOG.md
-rw-r--r--  1 jailuser jailuser  6258 Mar 22 11:20 CLAUDE.md
-rw-r--r--  1 jailuser jailuser  3101 Mar 22 11:20 CONTRIBUTING.md
-rw-r--r--  1 jailuser jailuser  8337 Mar 22 11:20 FAQ.md
-rw-r--r--  1 jailuser jailuser  7160 Mar 22 11:20 INSTALL.md
-rw-r--r--  1 jailuser jailuser 35121 Mar 22 11:20 LICENSE
-rw-r--r--  1 jailuser jailuser 19780 Mar 22 11:20 Makefile
-rw-r--r--  1 jailuser jailuser  8239 Mar 22 11:20 ProxySQL-3.0.4-Release-Notes.md
-rw-r--r--  1 jailuser jailuser  4294 Mar 22 11:20 RAG_COMPLETION_SUMMARY.md
-rw-r--r--  1 jailuser jailuser  2531 Mar 22 11:20 RAG_FILE_SUMMARY.md
-rw-r--r--  1 jailuser jailuser  4308 Mar 22 11:20 RAG_IMPLEMENTATION_COMPLETE.md
-rw-r--r--  1 jailuser jailuser  4320 Mar 22 11:20 RAG_IMPLEMENTATION_SUMMARY.md
drwxr-xr-x  2 jailuser jailuser   360 Mar 22 11:20 RAG_POC
-rw-r--r--  1 jailuser jailuser 16028 Mar 22 11:20 README.md
-rw-r--r--  1 jailuser jailuser  3024 Mar 22 11:20 RELEASE_NOTES_WORKFLOW.md
-rw-r--r--  1 jailuser jailuser  3496 Mar 22 11:20 RUNNING.md
-rw-r--r--  1 jailuser jailuser  1011 Mar 22 11:20 Read_Only_Testing.md
drwxr-xr-x  2 jailuser jailuser    60 Mar 22 11:20 binaries
-rw-r--r--  1 jailuser jailuser  1597 Mar 22 11:20 bounty.md
-rw-r--r--  1 jailuser jailuser    85 Mar 22 11:20 build_images.py
drwxr-xr-x  2 jailuser jailuser    80 Mar 22 11:20 common_mk
-rw-r--r--  1 jailuser jailuser   958 Mar 22 11:20 compile_examples.txt
drwxr-xr-x 25 jailuser jailuser   520 Mar 22 11:20 deps
drwxr-xr-x  2 jailuser jailuser   100 Mar 22 11:20 diamond
drwxr-xr-x 14 jailuser jailuser   780 Mar 22 11:20 doc
drwxr-xr-x  5 jailuser jailuser   100 Mar 22 11:20 docker
-rwxr-xr-x  1 jailuser jailuser 12876 Mar 22 11:20 docker-compose.yml
drwxr-xr-x  4 jailuser jailuser   100 Mar 22 11:20 etc
drwxr-xr-x  2 jailuser jailuser   160 Mar 22 11:20 genai_prototype
drwxr-xr-x  2 jailuser jailuser  2720 Mar 22 11:20 include
drwxr-xr-x  2 jailuser jailuser  2520 Mar 22 11:20 lib
drwxr-xr-x  2 jailuser jailuser    60 Mar 22 11:20 microbench
drwxr-xr-x  2 jailuser jailuser    60 Mar 22 11:20 notes
-rw-r--r--  1 jailuser jailuser   445 Mar 22 11:20 proxysql-tests.ini.example
-rw-r--r--  1 jailuser jailuser 11705 Mar 22 11:20 proxysql_notes.txt
-rwxr-xr-x  1 jailuser jailuser  5635 Mar 22 11:20 scenarios.py
drwxr-xr-x  7 jailuser jailuser   420 Mar 22 11:20 scripts
-rw-r--r--  1 jailuser jailuser  5578 Mar 22 11:20 simple_discovery.py
drwxr-xr-x  2 jailuser jailuser   200 Mar 22 11:20 src
drwxr-xr-x  3 jailuser jailuser    60 Mar 22 11:20 systemd
drwxr-xr-x 12 jailuser jailuser   280 Mar 22 11:20 test
drwxr-xr-x  3 jailuser jailuser    60 Mar 22 11:20 test-scripts
drwxr-xr-x  2 jailuser jailuser   160 Mar 22 11:20 tools

Script executed:

fd "Protocol" --type f

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

include/MySQLProtocolUtils.h
include/MySQL_Protocol.h
include/PgSQL_Protocol.h
lib/MySQLProtocolUtils.cpp
lib/MySQL_Protocol.cpp
lib/PgSQL_Protocol.cpp
test/tap/tests/test_PROXY_Protocol-t.cpp

Script executed:

cat lib/MySQLProtocolUtils.cpp 2>/dev/null | head -100

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

/**
 * `@file` MySQLProtocolUtils.cpp
 * `@brief` Implementation of MySQL protocol utility functions.
 *
 * `@see` MySQLProtocolUtils.h
 */

`#include` "MySQLProtocolUtils.h"
`#include` <cstring>

uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
	if (len == 0) return 0;
	uint8_t first_byte = buf[0];
	buf++; len--;
	if (first_byte < 0xFB) return first_byte;
	if (first_byte == 0xFC) {
		if (len < 2) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
		buf += 2; len -= 2;
		return value;
	}
	if (first_byte == 0xFD) {
		if (len < 3) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
			| (static_cast<uint64_t>(buf[2]) << 16);
		buf += 3; len -= 3;
		return value;
	}
	if (first_byte == 0xFE) {
		if (len < 8) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
			| (static_cast<uint64_t>(buf[2]) << 16)
			| (static_cast<uint64_t>(buf[3]) << 24)
			| (static_cast<uint64_t>(buf[4]) << 32)
			| (static_cast<uint64_t>(buf[5]) << 40)
			| (static_cast<uint64_t>(buf[6]) << 48)
			| (static_cast<uint64_t>(buf[7]) << 56);
		buf += 8; len -= 8;
		return value;
	}
	return 0;
}

size_t mysql_build_packet(
	const unsigned char *payload,
	uint32_t payload_len,
	uint8_t seq_id,
	unsigned char *out_buf)
{
	// 3-byte length (little-endian) + 1-byte sequence
	out_buf[0] = payload_len & 0xFF;
	out_buf[1] = (payload_len >> 8) & 0xFF;
	out_buf[2] = (payload_len >> 16) & 0xFF;
	out_buf[3] = seq_id;
	if (payload && payload_len > 0) {
		memcpy(out_buf + 4, payload, payload_len);
	}
	return payload_len + 4;
}

Script executed:

rg "payload_len" --type cpp | head -20

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

lib/MySQLProtocolUtils.cpp:	uint32_t payload_len,
lib/MySQLProtocolUtils.cpp:	out_buf[0] = payload_len & 0xFF;
lib/MySQLProtocolUtils.cpp:	out_buf[1] = (payload_len >> 8) & 0xFF;
lib/MySQLProtocolUtils.cpp:	out_buf[2] = (payload_len >> 16) & 0xFF;
lib/MySQLProtocolUtils.cpp:	if (payload && payload_len > 0) {
lib/MySQLProtocolUtils.cpp:		memcpy(out_buf + 4, payload, payload_len);
lib/MySQLProtocolUtils.cpp:	return payload_len + 4;
lib/PgSQL_Extended_Query_Message.cpp:	uint32_t payload_len = 0;
lib/PgSQL_Extended_Query_Message.cpp:	if (!get_uint32be(packet + offset, &payload_len)) {
lib/PgSQL_Extended_Query_Message.cpp:	if (payload_len != pkt_len - 1) {
lib/PgSQL_Extended_Query_Message.cpp:	uint32_t payload_len = 0;
lib/PgSQL_Extended_Query_Message.cpp:	if (!get_uint32be(packet + offset, &payload_len)) {
lib/PgSQL_Extended_Query_Message.cpp:	if (payload_len != pkt_len - 1) {
lib/PgSQL_Extended_Query_Message.cpp:	uint32_t payload_len = 0;
lib/PgSQL_Extended_Query_Message.cpp:	if (!get_uint32be(packet + offset, &payload_len)) {
lib/PgSQL_Extended_Query_Message.cpp:	if (payload_len != pkt_len - 1) {
lib/PgSQL_Extended_Query_Message.cpp:	uint32_t payload_len = 0;
lib/PgSQL_Extended_Query_Message.cpp:	if (!get_uint32be(packet + offset, &payload_len)) {
lib/PgSQL_Extended_Query_Message.cpp:	if (payload_len != pkt_len - 1) {
lib/PgSQL_Extended_Query_Message.cpp:	uint32_t payload_len = 0;

Script executed:

rg "memcpy.*payload" --type cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

lib/MySQLProtocolUtils.cpp:		memcpy(out_buf + 4, payload, payload_len);
lib/mysql_data_stream.cpp:			memcpy((unsigned char *)queueIN.pkt.ptr+sizeof(mysql_hdr), queue_r_ptr(queueIN), 3); // copy 3 bytes, the length of the uncompressed payload
lib/MySQL_Query_Cache.cpp:				memcpy(payload_temp, &warnings, sizeof(uint16_t));
lib/MySQL_Query_Cache.cpp:				memcpy((payload + 1), &warnings, sizeof(uint16_t));

Script executed:

fd "\.(cpp|h)$" --type f | head -30

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

RAG_POC/rag_ingest.cpp
deps/libscram/include/scram-internal.h
deps/libscram/include/scram.h
deps/sqlite3/sqlite-vec-source/sqlite-vec.h
genai_prototype/genai_demo.cpp
genai_prototype/genai_demo_event.cpp
include/AI_Features_Manager.h
include/AI_Tool_Handler.h
include/AI_Vector_Storage.h
include/Admin_Tool_Handler.h
include/Admin_ifaces.h
include/Anomaly_Detector.h
include/Base_HostGroups_Manager.h
include/Base_Session.h
include/Base_Thread.h
include/Cache_Tool_Handler.h
include/ClickHouse_Server.h
include/Command_Counter.h
include/Config_Tool_Handler.h
include/ConnectionPoolDecision.h
include/Discovery_Schema.h
include/GTID_Server_Data.h
include/GenAI_Thread.h
include/HostgroupRouting.h
include/LLM_Bridge.h
include/MCP_Endpoint.h
include/MCP_Thread.h
include/MCP_Tool_Handler.h
include/MonitorHealthDecision.h
include/MySQLProtocolUtils.h

Script executed:

ls -la lib/ 2>/dev/null || echo "lib directory not found"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

total 7004
drwxr-xr-x  2 jailuser jailuser   2520 Mar 22 11:20 .
drwxr-xr-x 23 jailuser jailuser   1020 Mar 22 11:20 ..
-rw-r--r--  1 jailuser jailuser  16628 Mar 22 11:20 AI_Features_Manager.cpp
-rw-r--r--  1 jailuser jailuser   6621 Mar 22 11:20 AI_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser   1018 Mar 22 11:20 AI_Vector_Storage.cpp
-rw-r--r--  1 jailuser jailuser  59785 Mar 22 11:20 Admin_Bootstrap.cpp
-rw-r--r--  1 jailuser jailuser  61130 Mar 22 11:20 Admin_FlushVariables.cpp
-rw-r--r--  1 jailuser jailuser 240330 Mar 22 11:20 Admin_Handler.cpp
-rw-r--r--  1 jailuser jailuser   4058 Mar 22 11:20 Admin_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser  31371 Mar 22 11:20 Anomaly_Detector.cpp
-rw-r--r--  1 jailuser jailuser   4051 Mar 22 11:20 BaseHGC.cpp
-rw-r--r--  1 jailuser jailuser   1770 Mar 22 11:20 BaseSrvList.cpp
-rw-r--r--  1 jailuser jailuser 196422 Mar 22 11:20 Base_HostGroups_Manager.cpp
-rw-r--r--  1 jailuser jailuser  30397 Mar 22 11:20 Base_Session.cpp
-rw-r--r--  1 jailuser jailuser  22148 Mar 22 11:20 Base_Thread.cpp
-rw-r--r--  1 jailuser jailuser   4462 Mar 22 11:20 Cache_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser 546903 Mar 22 11:20 Chart_bundle_js.cpp
-rw-r--r--  1 jailuser jailuser  13606 Mar 22 11:20 ClickHouse_Authentication.cpp
-rw-r--r--  1 jailuser jailuser  61042 Mar 22 11:20 ClickHouse_Server.cpp
-rw-r--r--  1 jailuser jailuser   7360 Mar 22 11:20 Config_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser 117045 Mar 22 11:20 Discovery_Schema.cpp
-rw-r--r--  1 jailuser jailuser  15290 Mar 22 11:20 GTID_Server_Data.cpp
-rw-r--r--  1 jailuser jailuser  59106 Mar 22 11:20 GenAI_Thread.cpp
-rw-r--r--  1 jailuser jailuser   1676 Mar 22 11:20 HostgroupRouting.cpp
-rw-r--r--  1 jailuser jailuser  11354 Mar 22 11:20 LLM_Bridge.cpp
-rw-r--r--  1 jailuser jailuser  22318 Mar 22 11:20 LLM_Clients.cpp
-rw-r--r--  1 jailuser jailuser  20608 Mar 22 11:20 MCP_Endpoint.cpp
-rw-r--r--  1 jailuser jailuser  12702 Mar 22 11:20 MCP_Thread.cpp
-rw-r--r--  1 jailuser jailuser   1636 Mar 22 11:20 MCP_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser   5805 Mar 22 11:20 Makefile
-rw-r--r--  1 jailuser jailuser   2853 Mar 22 11:20 MonitorHealthDecision.cpp
-rw-r--r--  1 jailuser jailuser  13622 Mar 22 11:20 MyHGC.cpp
-rw-r--r--  1 jailuser jailuser  12304 Mar 22 11:20 MySQLFFTO.cpp
-rw-r--r--  1 jailuser jailuser   1591 Mar 22 11:20 MySQLProtocolUtils.cpp
-rw-r--r--  1 jailuser jailuser  27952 Mar 22 11:20 MySQL_Authentication.cpp
-rw-r--r--  1 jailuser jailuser  19910 Mar 22 11:20 MySQL_Catalog.cpp
-rw-r--r--  1 jailuser jailuser  25861 Mar 22 11:20 MySQL_FTS.cpp
-rw-r--r--  1 jailuser jailuser 297234 Mar 22 11:20 MySQL_HostGroups_Manager.cpp
-rw-r--r--  1 jailuser jailuser  77618 Mar 22 11:20 MySQL_Logger.cpp
-rw-r--r--  1 jailuser jailuser 333917 Mar 22 11:20 MySQL_Monitor.cpp
-rw-r--r--  1 jailuser jailuser  31800 Mar 22 11:20 MySQL_PreparedStatement.cpp
-rw-r--r--  1 jailuser jailuser 110757 Mar 22 11:20 MySQL_Protocol.cpp
-rw-r--r--  1 jailuser jailuser  10224 Mar 22 11:20 MySQL_Query_Cache.cpp
-rw-r--r--  1 jailuser jailuser  34802 Mar 22 11:20 MySQL_Query_Processor.cpp
-rw-r--r--  1 jailuser jailuser  18417 Mar 22 11:20 MySQL_ResultSet.cpp
-rw-r--r--  1 jailuser jailuser 371282 Mar 22 11:20 MySQL_Session.cpp
-rw-r--r--  1 jailuser jailuser  24644 Mar 22 11:20 MySQL_Set_Stmt_Parser.cpp
-rw-r--r--  1 jailuser jailuser 254037 Mar 22 11:20 MySQL_Thread.cpp
-rw-r--r--  1 jailuser jailuser  41841 Mar 22 11:20 MySQL_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser  27149 Mar 22 11:20 MySQL_Variables.cpp
-rw-r--r--  1 jailuser jailuser   7092 Mar 22 11:20 MySQL_encode.cpp
-rw-r--r--  1 jailuser jailuser   5190 Mar 22 11:20 MySrvC.cpp
-rw-r--r--  1 jailuser jailuser  12498 Mar 22 11:20 MySrvConnList.cpp
-rw-r--r--  1 jailuser jailuser   4481 Mar 22 11:20 Observe_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser   1738 Mar 22 11:20 PgSQLCommandComplete.cpp
-rw-r--r--  1 jailuser jailuser  13107 Mar 22 11:20 PgSQLFFTO.cpp
-rw-r--r--  1 jailuser jailuser  22964 Mar 22 11:20 PgSQL_Authentication.cpp
-rw-r--r--  1 jailuser jailuser    551 Mar 22 11:20 PgSQL_Backend.cpp
-rw-r--r--  1 jailuser jailuser 101629 Mar 22 11:20 PgSQL_Connection.cpp
-rw-r--r--  1 jailuser jailuser  42071 Mar 22 11:20 PgSQL_Data_Stream.cpp
-rw-r--r--  1 jailuser jailuser  16573 Mar 22 11:20 PgSQL_Error_Helper.cpp
-rw-r--r--  1 jailuser jailuser  17191 Mar 22 11:20 PgSQL_ExplicitTxnStateMgr.cpp
-rw-r--r--  1 jailuser jailuser  18716 Mar 22 11:20 PgSQL_Extended_Query_Message.cpp
-rw-r--r--  1 jailuser jailuser 168643 Mar 22 11:20 PgSQL_HostGroups_Manager.cpp
-rw-r--r--  1 jailuser jailuser  52674 Mar 22 11:20 PgSQL_Logger.cpp
-rw-r--r--  1 jailuser jailuser  79293 Mar 22 11:20 PgSQL_Monitor.cpp
-rw-r--r--  1 jailuser jailuser  22063 Mar 22 11:20 PgSQL_PreparedStatement.cpp
-rw-r--r--  1 jailuser jailuser  84284 Mar 22 11:20 PgSQL_Protocol.cpp
-rw-r--r--  1 jailuser jailuser   1876 Mar 22 11:20 PgSQL_Query_Cache.cpp
-rw-r--r--  1 jailuser jailuser  47636 Mar 22 11:20 PgSQL_Query_Processor.cpp
-rw-r--r--  1 jailuser jailuser 284549 Mar 22 11:20 PgSQL_Session.cpp
-rw-r--r--  1 jailuser jailuser   6269 Mar 22 11:20 PgSQL_Set_Stmt_Parser.cpp
-rw-r--r--  1 jailuser jailuser  22376 Mar 22 11:20 PgSQL_Static_Harvester.cpp
-rw-r--r--  1 jailuser jailuser 226855 Mar 22 11:20 PgSQL_Thread.cpp
-rw-r--r--  1 jailuser jailuser  10310 Mar 22 11:20 PgSQL_Variables.cpp
-rw-r--r--  1 jailuser jailuser  18981 Mar 22 11:20 PgSQL_Variables_Validator.cpp
-rw-r--r--  1 jailuser jailuser 367924 Mar 22 11:20 ProxySQL_Admin.cpp
-rw-r--r--  1 jailuser jailuser  46011 Mar 22 11:20 ProxySQL_Admin_Disk_Upgrade.cpp
-rw-r--r--  1 jailuser jailuser   5337 Mar 22 11:20 ProxySQL_Admin_Scheduler.cpp
-rw-r--r--  1 jailuser jailuser 134969 Mar 22 11:20 ProxySQL_Admin_Stats.cpp
-rw-r--r--  1 jailuser jailuser   5242 Mar 22 11:20 ProxySQL_Admin_Tests.cpp
-rw-r--r--  1 jailuser jailuser  51659 Mar 22 11:20 ProxySQL_Admin_Tests2.cpp
-rw-r--r--  1 jailuser jailuser 213104 Mar 22 11:20 ProxySQL_Cluster.cpp
-rw-r--r--  1 jailuser jailuser  87278 Mar 22 11:20 ProxySQL_Config.cpp
-rw-r--r--  1 jailuser jailuser  24183 Mar 22 11:20 ProxySQL_GloVars.cpp
-rw-r--r--  1 jailuser jailuser  33434 Mar 22 11:20 ProxySQL_HTTP_Server.cpp
-rw-r--r--  1 jailuser jailuser  10316 Mar 22 11:20 ProxySQL_MCP_Server.cpp
-rw-r--r--  1 jailuser jailuser  10741 Mar 22 11:20 ProxySQL_Poll.cpp
-rw-r--r--  1 jailuser jailuser  20309 Mar 22 11:20 ProxySQL_RESTAPI_Server.cpp
-rw-r--r--  1 jailuser jailuser   2674 Mar 22 11:20 ProxySQL_Restapi.cpp
-rw-r--r--  1 jailuser jailuser  77345 Mar 22 11:20 ProxySQL_Statistics.cpp
-rw-r--r--  1 jailuser jailuser   4829 Mar 22 11:20 QP_query_digest_stats.cpp
-rw-r--r--  1 jailuser jailuser    633 Mar 22 11:20 QP_rule_text.cpp
-rw-r--r--  1 jailuser jailuser  22150 Mar 22 11:20 Query_Cache.cpp
-rw-r--r--  1 jailuser jailuser  99447 Mar 22 11:20 Query_Processor.cpp
-rw-r--r--  1 jailuser jailuser  98297 Mar 22 11:20 Query_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser  89831 Mar 22 11:20 RAG_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser  61586 Mar 22 11:20 SHA-crypt.txt
-rw-r--r--  1 jailuser jailuser   8529 Mar 22 11:20 SpookyV2.cpp
-rw-r--r--  1 jailuser jailuser  44063 Mar 22 11:20 Static_Harvester.cpp
-rw-r--r--  1 jailuser jailuser 128857 Mar 22 11:20 Stats_Tool_Handler.cpp
-rw-r--r--  1 jailuser jailuser   6967 Mar 22 11:20 TSDB_Dashboard_html.cpp
-rw-r--r--  1 jailuser jailuser   1383 Mar 22 11:20 TransactionState.cpp
-rw-r--r--  1 jailuser jailuser  88371 Mar 22 11:20 c_tokenizer.cpp
-rw-r--r--  1 jailuser jailuser  13865 Mar 22 11:20 c_tokenizer_legacy.cpp
-rw-r--r--  1 jailuser jailuser   2023 Mar 22 11:20 configfile.cpp
-rw-r--r--  1 jailuser jailuser  19210 Mar 22 11:20 debug.cpp
-rw-r--r--  1 jailuser jailuser  33070 Mar 22 11:20 font-awesome.min.css.cpp
-rw-r--r--  1 jailuser jailuser  12786 Mar 22 11:20 gen_utils.cpp
-rw-r--r--  1 jailuser jailuser   3534 Mar 22 11:20 log_utils.cpp
-rw-r--r--  1 jailuser jailuser  44145 Mar 22 11:20 main-bundle.min.css.cpp
-rw-r--r--  1 jailuser jailuser    584 Mar 22 11:20 mysql_backend.cpp
-rw-r--r--  1 jailuser jailuser 110159 Mar 22 11:20 mysql_connection.cpp
-rw-r--r--  1 jailuser jailuser  66287 Mar 22 11:20 mysql_data_stream.cpp
-rw-r--r--  1 jailuser jailuser   4256 Mar 22 11:20 network.cpp
-rw-r--r--  1 jailuser jailuser 107532 Mar 22 11:20 pgsql_tokenizer.cpp
-rw-r--r--  1 jailuser jailuser  14677 Mar 22 11:20 proxy_protocol_info.cpp
-rw-r--r--  1 jailuser jailuser   3902 Mar 22 11:20 proxy_sqlite3_symbols.cpp
-rw-r--r--  1 jailuser jailuser   3103 Mar 22 11:20 proxysql_coredump.cpp
-rw-r--r--  1 jailuser jailuser   4492 Mar 22 11:20 proxysql_find_charset.cpp
-rw-r--r--  1 jailuser jailuser   7567 Mar 22 11:20 proxysql_sslkeylog.cpp
-rw-r--r--  1 jailuser jailuser  25244 Mar 22 11:20 proxysql_utils.cpp
-rw-r--r--  1 jailuser jailuser  17818 Mar 22 11:20 sha256crypt.cpp
-rw-r--r--  1 jailuser jailuser  21970 Mar 22 11:20 sha256crypt.orig.c
-rw-r--r--  1 jailuser jailuser  38397 Mar 22 11:20 sqlite3db.cpp
-rw-r--r--  1 jailuser jailuser   2048 Mar 22 11:20 thread.cpp

Script executed:

git ls-files | wc -l && git ls-files | head -50

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

1475
.aider.conf.yml
.github/ISSUE_TEMPLATE/bug_report.md
.github/issue_template.md
.github/workflows/CI-3p-aiomysql.yml
.github/workflows/CI-3p-django-framework.yml
.github/workflows/CI-3p-laravel-framework.yml
.github/workflows/CI-3p-mariadb-connector-c.yml
.github/workflows/CI-3p-mysql-connector-j.yml
.github/workflows/CI-3p-pgjdbc.yml
.github/workflows/CI-3p-php-pdo-mysql.yml
.github/workflows/CI-3p-php-pdo-pgsql.yml
.github/workflows/CI-3p-postgresql.yml
.github/workflows/CI-3p-sqlalchemy.yml
.github/workflows/CI-basictests.yml
.github/workflows/CI-builds.yml
.github/workflows/CI-codeql.yml
.github/workflows/CI-maketest.yml
.github/workflows/CI-package-build.yml
.github/workflows/CI-repltests.yml
.github/workflows/CI-selftests.yml
.github/workflows/CI-shuntest.yml
.github/workflows/CI-taptests-asan.yml
.github/workflows/CI-taptests-groups.yml
.github/workflows/CI-taptests-ssl.yml
.github/workflows/CI-taptests.yml
.github/workflows/CI-trigger.yml
.gitignore
Aurora_Automated_Testing.md
CHANGELOG.md
CLAUDE.md
CONTRIBUTING.md
FAQ.md
INSTALL.md
LICENSE
Makefile
ProxySQL-3.0.4-Release-Notes.md
RAG_COMPLETION_SUMMARY.md
RAG_FILE_SUMMARY.md
RAG_IMPLEMENTATION_COMPLETE.md
RAG_IMPLEMENTATION_SUMMARY.md
RAG_POC/CHUNKING_AND_EMBEDDING_GUIDE.md
RAG_POC/EMBEDDING_TEST_PLAN.md
RAG_POC/INGEST_USAGE_GUIDE.md
RAG_POC/Makefile
RAG_POC/architecture-data-model.md
RAG_POC/architecture-runtime-retrieval.md
RAG_POC/embeddings-design.md
RAG_POC/mcp-tools.md
RAG_POC/rag_ingest.cpp
RAG_POC/rag_system_prompt.md

MySQL protocol packet header format 3-byte payload length limit 0xFFFFFF


The MySQL protocol packet header is 4 bytes long: the first 3 bytes encode the payload length as a little-endian unsigned integer (maximum value 0xFFFFFF or 16,777,215 bytes, or 16MB-1 byte), followed by 1 byte for the sequence ID. The payload follows the header. For payloads larger than 0xFFFFFF bytes, the data is split across multiple packets, where packets with length 0xFFFFFF continue the data, and the sequence ID increments for each subsequent packet until the final one.

Citations:


Script executed:

grep -r "mysql_build_packet" --include="*.cpp" --include="*.h" -B 3 -A 3 | head -100

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

include/MySQLProtocolUtils.h- * `@param` out_buf      Output buffer (must be at least payload_len + 4).
include/MySQLProtocolUtils.h- * `@return` Total packet size (payload_len + 4).
include/MySQLProtocolUtils.h- */
include/MySQLProtocolUtils.h:size_t mysql_build_packet(
include/MySQLProtocolUtils.h-	const unsigned char *payload,
include/MySQLProtocolUtils.h-	uint32_t payload_len,
include/MySQLProtocolUtils.h-	uint8_t seq_id,
--
lib/MySQLProtocolUtils.cpp-	return 0;
lib/MySQLProtocolUtils.cpp-}
lib/MySQLProtocolUtils.cpp-
lib/MySQLProtocolUtils.cpp:size_t mysql_build_packet(
lib/MySQLProtocolUtils.cpp-	const unsigned char *payload,
lib/MySQLProtocolUtils.cpp-	uint32_t payload_len,
lib/MySQLProtocolUtils.cpp-	uint8_t seq_id,
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-// 2. MySQL: packet building
test/tap/tests/unit/ffto_protocol_unit-t.cpp-// ============================================================================
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp:static void test_mysql_build_packet() {
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char out[13];
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload, 9, 0, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(total == 13, "build packet: total size 13");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(out[0] == 9 && out[1] == 0 && out[2] == 0, "build packet: length = 9");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(out[3] == 0, "build packet: seq_id = 0");
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	// Build a packet with 1000-byte payload
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	std::vector<unsigned char> payload(1000, 'X');
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	std::vector<unsigned char> out(1004);
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(total == 1004, "large packet: total size 1004");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(out[0] == 0xE8 && out[1] == 0x03 && out[2] == 0x00,
test/tap/tests/unit/ffto_protocol_unit-t.cpp-		"large packet: length = 1000 (little-endian)");
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-static void test_mysql_build_empty_packet() {
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char out[4];
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	size_t total = mysql_build_packet(nullptr, 0, 1, out);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(total == 4, "empty packet: header only");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(out[0] == 0 && out[1] == 0 && out[2] == 0, "empty packet: length = 0");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-}
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	// affected_rows = 42
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char ok_payload[] = {0x00, 42, 0};  // OK, affected=42, last_insert=0
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char pkt[7];
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	mysql_build_packet(ok_payload, 3, 1, pkt);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	// Parse affected_rows from the OK packet payload
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	const unsigned char *pos = ok_payload + 1;
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	size_t offset = 0;
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char p1[] = {0x03, 'S', 'E', 'L'};
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p1, 4, 0, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char p2[] = {0x01, 0x02, 0x03};
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p2, 3, 1, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	unsigned char p3[] = {0xFE, 0x00, 0x00, 0x00, 0x00};
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	offset += mysql_build_packet(p3, 5, 2, stream + offset);
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	ok(offset == 4+4 + 3+4 + 5+4, "multi-packet: total stream size correct");
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
--
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	test_mysql_lenenc_truncated();     // 2
test/tap/tests/unit/ffto_protocol_unit-t.cpp-
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	// MySQL packet building
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	test_mysql_build_packet();         // 4
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	test_mysql_build_large_packet();   // 3
test/tap/tests/unit/ffto_protocol_unit-t.cpp-	test_mysql_build_empty_packet();   // 2
test/tap/tests/unit/ffto_protocol_unit-t.cpp-

Script executed:

sed -n '1,200p' test/tap/tests/unit/ffto_protocol_unit-t.cpp | grep -A 50 "test_mysql_build_packet"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

static void test_mysql_build_packet() {
	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
	unsigned char out[13];
	size_t total = mysql_build_packet(payload, 9, 0, out);
	ok(total == 13, "build packet: total size 13");
	ok(out[0] == 9 && out[1] == 0 && out[2] == 0, "build packet: length = 9");
	ok(out[3] == 0, "build packet: seq_id = 0");
	ok(memcmp(out + 4, payload, 9) == 0, "build packet: payload intact");
}

static void test_mysql_build_large_packet() {
	// Build a packet with 1000-byte payload
	std::vector<unsigned char> payload(1000, 'X');
	std::vector<unsigned char> out(1004);
	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
	ok(total == 1004, "large packet: total size 1004");
	ok(out[0] == 0xE8 && out[1] == 0x03 && out[2] == 0x00,
		"large packet: length = 1000 (little-endian)");
	ok(out[3] == 5, "large packet: seq_id = 5");
}

static void test_mysql_build_empty_packet() {
	unsigned char out[4];
	size_t total = mysql_build_packet(nullptr, 0, 1, out);
	ok(total == 4, "empty packet: header only");
	ok(out[0] == 0 && out[1] == 0 && out[2] == 0, "empty packet: length = 0");
}

// ============================================================================
// 3. MySQL: OK packet affected_rows extraction
// ============================================================================

static void test_mysql_ok_affected_rows() {
	// Build an OK packet: 0x00 + affected_rows(lenenc) + last_insert_id(lenenc)
	// affected_rows = 42
	unsigned char ok_payload[] = {0x00, 42, 0};  // OK, affected=42, last_insert=0
	unsigned char pkt[7];
	mysql_build_packet(ok_payload, 3, 1, pkt);

	// Parse affected_rows from the OK packet payload
	const unsigned char *pos = ok_payload + 1;
	size_t rem = 2;
	uint64_t affected = mysql_read_lenenc_int(pos, rem);
	ok(affected == 42, "OK packet: affected_rows = 42");
}

static void test_mysql_ok_large_affected_rows() {
	// affected_rows = 300 (needs 0xFC prefix)
	unsigned char ok_payload[] = {0x00, 0xFC, 0x2C, 0x01, 0};
	const unsigned char *pos = ok_payload + 1;
	size_t rem = 4;

Script executed:

cat include/MySQLProtocolUtils.h

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

/**
 * `@file` MySQLProtocolUtils.h
 * `@brief` Pure MySQL protocol utility functions for unit testability.
 *
 * Extracted from MySQLFFTO for testing. These are low-level protocol
 * parsing helpers that operate on raw byte buffers.
 *
 * `@see` FFTO unit testing (GitHub issue `#5499`)
 */

`#ifndef` MYSQL_PROTOCOL_UTILS_H
`#define` MYSQL_PROTOCOL_UTILS_H

`#include` <cstdint>
`#include` <cstddef>

/**
 * `@brief` Read a MySQL length-encoded integer from a buffer.
 *
 * MySQL length-encoded integers use 1-9 bytes:
 *   - 0x00-0xFA: 1 byte (value itself)
 *   - 0xFC: 2 bytes follow (uint16)
 *   - 0xFD: 3 bytes follow (uint24)
 *   - 0xFE: 8 bytes follow (uint64)
 *
 * `@param` buf  [in/out] Pointer to current position; advanced past the integer.
 * `@param` len  [in/out] Remaining buffer length; decremented.
 * `@return` Decoded 64-bit integer value (0 on error/truncation).
 */
uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len);

/**
 * `@brief` Build a MySQL protocol packet header + payload.
 *
 * Constructs a complete MySQL wire-format packet: 3-byte length +
 * 1-byte sequence number + payload.
 *
 * `@param` payload      Payload data.
 * `@param` payload_len  Length of payload.
 * `@param` seq_id       Packet sequence number.
 * `@param` out_buf      Output buffer (must be at least payload_len + 4).
 * `@return` Total packet size (payload_len + 4).
 */
size_t mysql_build_packet(
	const unsigned char *payload,
	uint32_t payload_len,
	uint8_t seq_id,
	unsigned char *out_buf
);

`#endif` // MYSQL_PROTOCOL_UTILS_H

Script executed:

grep -r "mysql_build_packet" --include="*.cpp" | grep -v "test" | grep -v ".cpp:.*mysql_build_packet(" | head -5

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0


Script executed:

cat -n lib/MySQLProtocolUtils.cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

     1	/**
     2	 * `@file` MySQLProtocolUtils.cpp
     3	 * `@brief` Implementation of MySQL protocol utility functions.
     4	 *
     5	 * `@see` MySQLProtocolUtils.h
     6	 */
     7	
     8	`#include` "MySQLProtocolUtils.h"
     9	`#include` <cstring>
    10	
    11	uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
    12		if (len == 0) return 0;
    13		uint8_t first_byte = buf[0];
    14		buf++; len--;
    15		if (first_byte < 0xFB) return first_byte;
    16		if (first_byte == 0xFC) {
    17			if (len < 2) return 0;
    18			uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
    19			buf += 2; len -= 2;
    20			return value;
    21		}
    22		if (first_byte == 0xFD) {
    23			if (len < 3) return 0;
    24			uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
    25				| (static_cast<uint64_t>(buf[2]) << 16);
    26			buf += 3; len -= 3;
    27			return value;
    28		}
    29		if (first_byte == 0xFE) {
    30			if (len < 8) return 0;
    31			uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
    32				| (static_cast<uint64_t>(buf[2]) << 16)
    33				| (static_cast<uint64_t>(buf[3]) << 24)
    34				| (static_cast<uint64_t>(buf[4]) << 32)
    35				| (static_cast<uint64_t>(buf[5]) << 40)
    36				| (static_cast<uint64_t>(buf[6]) << 48)
    37				| (static_cast<uint64_t>(buf[7]) << 56);
    38			buf += 8; len -= 8;
    39			return value;
    40		}
    41		return 0;
    42	}
    43	
    44	size_t mysql_build_packet(
    45		const unsigned char *payload,
    46		uint32_t payload_len,
    47		uint8_t seq_id,
    48		unsigned char *out_buf)
    49	{
    50		// 3-byte length (little-endian) + 1-byte sequence
    51		out_buf[0] = payload_len & 0xFF;
    52		out_buf[1] = (payload_len >> 8) & 0xFF;
    53		out_buf[2] = (payload_len >> 16) & 0xFF;
    54		out_buf[3] = seq_id;
    55		if (payload && payload_len > 0) {
    56			memcpy(out_buf + 4, payload, payload_len);
    57		}
    58		return payload_len + 4;
    59	}

Script executed:

grep -A 100 "test_mysql_build_packet" test/tap/tests/unit/ffto_protocol_unit-t.cpp | head -60

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

static void test_mysql_build_packet() {
	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
	unsigned char out[13];
	size_t total = mysql_build_packet(payload, 9, 0, out);
	ok(total == 13, "build packet: total size 13");
	ok(out[0] == 9 && out[1] == 0 && out[2] == 0, "build packet: length = 9");
	ok(out[3] == 0, "build packet: seq_id = 0");
	ok(memcmp(out + 4, payload, 9) == 0, "build packet: payload intact");
}

static void test_mysql_build_large_packet() {
	// Build a packet with 1000-byte payload
	std::vector<unsigned char> payload(1000, 'X');
	std::vector<unsigned char> out(1004);
	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
	ok(total == 1004, "large packet: total size 1004");
	ok(out[0] == 0xE8 && out[1] == 0x03 && out[2] == 0x00,
		"large packet: length = 1000 (little-endian)");
	ok(out[3] == 5, "large packet: seq_id = 5");
}

static void test_mysql_build_empty_packet() {
	unsigned char out[4];
	size_t total = mysql_build_packet(nullptr, 0, 1, out);
	ok(total == 4, "empty packet: header only");
	ok(out[0] == 0 && out[1] == 0 && out[2] == 0, "empty packet: length = 0");
}

// ============================================================================
// 3. MySQL: OK packet affected_rows extraction
// ============================================================================

static void test_mysql_ok_affected_rows() {
	// Build an OK packet: 0x00 + affected_rows(lenenc) + last_insert_id(lenenc)
	// affected_rows = 42
	unsigned char ok_payload[] = {0x00, 42, 0};  // OK, affected=42, last_insert=0
	unsigned char pkt[7];
	mysql_build_packet(ok_payload, 3, 1, pkt);

	// Parse affected_rows from the OK packet payload
	const unsigned char *pos = ok_payload + 1;
	size_t rem = 2;
	uint64_t affected = mysql_read_lenenc_int(pos, rem);
	ok(affected == 42, "OK packet: affected_rows = 42");
}

static void test_mysql_ok_large_affected_rows() {
	// affected_rows = 300 (needs 0xFC prefix)
	unsigned char ok_payload[] = {0x00, 0xFC, 0x2C, 0x01, 0};
	const unsigned char *pos = ok_payload + 1;
	size_t rem = 4;
	uint64_t affected = mysql_read_lenenc_int(pos, rem);
	ok(affected == 300, "OK packet: affected_rows = 300 (2-byte lenenc)");
}

// ============================================================================
// 4. PgSQL: CommandComplete — extended tests
// ============================================================================

static void test_pgsql_insert_with_oid() {

Script executed:

python3 << 'EOF'
with open('test/tap/tests/unit/ffto_protocol_unit-t.cpp', 'r') as f:
    content = f.read()
    
# Find the test_mysql_build_packet function
start = content.find('static void test_mysql_build_packet()')
if start != -1:
    end = content.find('\n}', start) + 2
    test_func = content[start:end]
    print(test_func)
EOF

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

static void test_mysql_build_packet() {
	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
	unsigned char out[13];
	size_t total = mysql_build_packet(payload, 9, 0, out);
	ok(total == 13, "build packet: total size 13");
	ok(out[0] == 9 && out[1] == 0 && out[2] == 0, "build packet: length = 9");
	ok(out[3] == 0, "build packet: seq_id = 0");
	ok(memcmp(out + 4, payload, 9) == 0, "build packet: payload intact");
}

Script executed:

rg "0xFFFFFF|16.*777|16.*MB" --type cpp | head -20

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

test/tap/tests/mysql_stmt_send_long_data_large-t.cpp:This test needs to be extended to send packets smaller than 4MB, but also 16MB or larger.
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp: *   2. Fills this table with random length strings, varying from small to higher than 0xFFFFFF size.
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp:			row_lens.push_back(small_value_dst(gen) + 0xFFFFFF*2);
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp:			row_lens.push_back(small_value_dst(gen) + 0xFFFFFF);
test/tap/tests/set_testing-240.h:			ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/set_testing.h:			ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/test_ps_large_result-t.cpp:	ok(true, "16MB row query is executed");
test/tap/tests/test_ps_large_result-t.cpp:	ok(true, "16MB result binded");
test/tap/tests/test_ps_large_result-t.cpp:	ok(true, "16MB result stored");
test/tap/tests/test_ps_large_result-t.cpp:	ok(row_count3==10, "Fetched 10 rows. Some of them are 16MB long");
test/tap/tests/unit/ffto_protocol_unit-t.cpp:	ok(mysql_read_lenenc_int(p, len) == 0xFFFFFFFF, "lenenc 8-byte: → 4294967295");
test/tap/tests/test_com_reset_connection_com_change_user-t.cpp:				ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/unit/protocol_unit-t.cpp:	ok(len == 0xFFFFFFFF && bytes == 9,
test/tap/tests/unit/protocol_unit-t.cpp:	// 3-byte range (65536 - 16777215)
test/tap/tests/unit/protocol_unit-t.cpp:	// 8-byte range (>= 16777216)
test/tap/tests/unit/protocol_unit-t.cpp:	enc_len = mysql_encode_length(16777216, hd);
test/tap/tests/unit/protocol_unit-t.cpp:		"encode_length: 16777216 → 9 bytes (0xFE prefix)");
test/tap/tests/unit/protocol_unit-t.cpp:	uint64_t test_values[] = {0, 1, 250, 251, 1000, 65535, 65536, 16777215, 16777216, 100000000ULL};
test/tap/tests/unit/protocol_unit-t.cpp:	hdr.pkt_length = 0xFFFFFF;
test/tap/tests/unit/protocol_unit-t.cpp:	ok(hdr.pkt_length == 0xFFFFFF,

Script executed:

rg "payload.*size.*limit|max.*payload|0xFFFFFF" --type cpp | head -20

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

test/tap/tests/set_testing-240.h:			ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/test_com_reset_connection_com_change_user-t.cpp:				ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/set_testing.h:			ss << 0xFFFFFFFFFFFFFFFF;
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp: *   2. Fills this table with random length strings, varying from small to higher than 0xFFFFFF size.
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp:			row_lens.push_back(small_value_dst(gen) + 0xFFFFFF*2);
test/tap/tests/reg_test_3690-admin_large_pkts-t.cpp:			row_lens.push_back(small_value_dst(gen) + 0xFFFFFF);
lib/MySQL_FTS.cpp:		snprintf(hash_suffix, sizeof(hash_suffix), "%08zx", hash_value & 0xFFFFFFFF);
lib/mysql_connection.cpp:						if (r->length > 0xFFFFFF) {
lib/mysql_connection.cpp:							total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr);
lib/mysql_connection.cpp:							if (r->length > 0xFFFFFF) {
lib/mysql_connection.cpp:								total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr);
lib/mysql_connection.cpp:		if (ir->length > 0xFFFFFF) {
lib/mysql_connection.cpp:			total_size+=(ir->length / 0xFFFFFF) * sizeof(mysql_hdr);
lib/proxy_protocol_info.cpp:		uint32_t subnet = ntohl(subnet_addr.v4.s_addr) & (0xFFFFFFFF << (32 - mask));
lib/PgSQL_Protocol.cpp:				column_value_len = -1; /*0xFFFFFFFF*/
lib/PgSQL_Session.cpp:	if (pkt.size == (0xFFFFFF + sizeof(mysql_hdr))) { // there are more packets
lib/PgSQL_Session.cpp:			if (pkt.size == (0xFFFFFF + sizeof(mysql_hdr))) {
lib/MySQL_ResultSet.cpp:			if (r->length > 0xFFFFFF) {
lib/MySQL_ResultSet.cpp:				total_size+=(r->length / 0xFFFFFF) * sizeof(mysql_hdr);
lib/MySQL_ResultSet.cpp:				if (r->length > 0xFFFFFF) {

Script executed:

wc -l lib/MySQL_Protocol.cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

3069 lib/MySQL_Protocol.cpp

Script executed:

grep -B 10 "mysql_build_packet" test/tap/tests/unit/ffto_protocol_unit-t.cpp | grep -A 10 "1000\|0xFFFFFF"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

	// Build a packet with 1000-byte payload
	std::vector<unsigned char> payload(1000, 'X');
	std::vector<unsigned char> out(1004);
	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
	ok(total == 1004, "large packet: total size 1004");
	ok(out[0] == 0xE8 && out[1] == 0x03 && out[2] == 0x00,
		"large packet: length = 1000 (little-endian)");
	ok(out[3] == 5, "large packet: seq_id = 5");
}

static void test_mysql_build_empty_packet() {
	unsigned char out[4];
	size_t total = mysql_build_packet(nullptr, 0, 1, out);
--

// ============================================================================
// 3. MySQL: OK packet affected_rows extraction

Script executed:

sed -n '/static void test_mysql_build_packet/,/^}/p' test/tap/tests/unit/ffto_protocol_unit-t.cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

static void test_mysql_build_packet() {
	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
	unsigned char out[13];
	size_t total = mysql_build_packet(payload, 9, 0, out);
	ok(total == 13, "build packet: total size 13");
	ok(out[0] == 9 && out[1] == 0 && out[2] == 0, "build packet: length = 9");
	ok(out[3] == 0, "build packet: seq_id = 0");
	ok(memcmp(out + 4, payload, 9) == 0, "build packet: payload intact");
}

Script executed:

find . -name "*.md" -type f -exec grep -l "payload\|packet.*size" {} \; | head -5

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

./CHANGELOG.md
./RAG_POC/architecture-runtime-retrieval.md
./doc/ffto_design.md
./doc/ssl_keylog/ssl_keylog_user_guide.md
./scripts/mcp/DiscoveryAgent/ClaudeCode_Headless/prompts/two_phase_discovery_prompt.md

stderr:

find: 'grep' terminated by signal 13

Script executed:

head -100 lib/MySQL_Protocol.cpp | grep -i "build\|packet\|payload"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

`#ifdef` max_allowed_packet
`#undef` max_allowed_packet

Script executed:

grep -B 5 "mysql_build_packet" test/tap/tests/unit/ffto_protocol_unit-t.cpp | grep -E "payload_len|1000|4|3|9"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

	unsigned char payload[] = {0x03, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '1'};
	unsigned char out[13];
	size_t total = mysql_build_packet(payload, 9, 0, out);
	// Build a packet with 1000-byte payload
	std::vector<unsigned char> payload(1000, 'X');
	std::vector<unsigned char> out(1004);
	size_t total = mysql_build_packet(payload.data(), 1000, 5, out.data());
	ok(out[3] == 5, "large packet: seq_id = 5");
	unsigned char out[4];
	// affected_rows = 42
	unsigned char ok_payload[] = {0x00, 42, 0};  // OK, affected=42, last_insert=0
	mysql_build_packet(ok_payload, 3, 1, pkt);
	// Build 3 packets sequentially (simulating a multi-packet stream)
	unsigned char stream[64];
	unsigned char p1[] = {0x03, 'S', 'E', 'L'};
	offset += mysql_build_packet(p1, 4, 0, stream + offset);
	unsigned char p2[] = {0x01, 0x02, 0x03};
	offset += mysql_build_packet(p2, 3, 1, stream + offset);
	unsigned char p3[] = {0xFE, 0x00, 0x00, 0x00, 0x00};
	offset += mysql_build_packet(p3, 5, 2, stream + offset);
	test_mysql_lenenc_3byte();         // 1
	test_mysql_build_packet();         // 4

Script executed:

grep -A 5 "size_t mysql_build_packet" lib/MySQLProtocolUtils.cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

size_t mysql_build_packet(
	const unsigned char *payload,
	uint32_t payload_len,
	uint8_t seq_id,
	unsigned char *out_buf)
{

Script executed:

head -50 lib/MySQLProtocolUtils.cpp

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

/**
 * `@file` MySQLProtocolUtils.cpp
 * `@brief` Implementation of MySQL protocol utility functions.
 *
 * `@see` MySQLProtocolUtils.h
 */

`#include` "MySQLProtocolUtils.h"
`#include` <cstring>

uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len) {
	if (len == 0) return 0;
	uint8_t first_byte = buf[0];
	buf++; len--;
	if (first_byte < 0xFB) return first_byte;
	if (first_byte == 0xFC) {
		if (len < 2) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8);
		buf += 2; len -= 2;
		return value;
	}
	if (first_byte == 0xFD) {
		if (len < 3) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
			| (static_cast<uint64_t>(buf[2]) << 16);
		buf += 3; len -= 3;
		return value;
	}
	if (first_byte == 0xFE) {
		if (len < 8) return 0;
		uint64_t value = buf[0] | (static_cast<uint64_t>(buf[1]) << 8)
			| (static_cast<uint64_t>(buf[2]) << 16)
			| (static_cast<uint64_t>(buf[3]) << 24)
			| (static_cast<uint64_t>(buf[4]) << 32)
			| (static_cast<uint64_t>(buf[5]) << 40)
			| (static_cast<uint64_t>(buf[6]) << 48)
			| (static_cast<uint64_t>(buf[7]) << 56);
		buf += 8; len -= 8;
		return value;
	}
	return 0;
}

size_t mysql_build_packet(
	const unsigned char *payload,
	uint32_t payload_len,
	uint8_t seq_id,
	unsigned char *out_buf)
{
	// 3-byte length (little-endian) + 1-byte sequence

Script executed:

cat include/MySQLProtocolUtils.h | grep -A 10 "mysql_build_packet"

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

size_t mysql_build_packet(
	const unsigned char *payload,
	uint32_t payload_len,
	uint8_t seq_id,
	unsigned char *out_buf
);

`#endif` // MYSQL_PROTOCOL_UTILS_H

Script executed:

cat -n include/MySQLProtocolUtils.h

Repository: sysown/proxysql


Repository: sysown/proxysql
Exit code: 0

stdout:

     1	/**
     2	 * `@file` MySQLProtocolUtils.h
     3	 * `@brief` Pure MySQL protocol utility functions for unit testability.
     4	 *
     5	 * Extracted from MySQLFFTO for testing. These are low-level protocol
     6	 * parsing helpers that operate on raw byte buffers.
     7	 *
     8	 * `@see` FFTO unit testing (GitHub issue `#5499`)
     9	 */
    10	
    11	`#ifndef` MYSQL_PROTOCOL_UTILS_H
    12	`#define` MYSQL_PROTOCOL_UTILS_H
    13	
    14	`#include` <cstdint>
    15	`#include` <cstddef>
    16	
    17	/**
    18	 * `@brief` Read a MySQL length-encoded integer from a buffer.
    19	 *
    20	 * MySQL length-encoded integers use 1-9 bytes:
    21	 *   - 0x00-0xFA: 1 byte (value itself)
    22	 *   - 0xFC: 2 bytes follow (uint16)
    23	 *   - 0xFD: 3 bytes follow (uint24)
    24	 *   - 0xFE: 8 bytes follow (uint64)
    25	 *
    26	 * `@param` buf  [in/out] Pointer to current position; advanced past the integer.
    27	 * `@param` len  [in/out] Remaining buffer length; decremented.
    28	 * `@return` Decoded 64-bit integer value (0 on error/truncation).
    29	 */
    30	uint64_t mysql_read_lenenc_int(const unsigned char* &buf, size_t &len);
    31	
    32	/**
    33	 * `@brief` Build a MySQL protocol packet header + payload.
    34	 *
    35	 * Constructs a complete MySQL wire-format packet: 3-byte length +
    36	 * 1-byte sequence number + payload.
    37	 *
    38	 * `@param` payload      Payload data.
    39	 * `@param` payload_len  Length of payload.
    40	 * `@param` seq_id       Packet sequence number.
    41	 * `@param` out_buf      Output buffer (must be at least payload_len + 4).
    42	 * `@return` Total packet size (payload_len + 4).
    43	 */
    44	size_t mysql_build_packet(
    45		const unsigned char *payload,
    46		uint32_t payload_len,
    47		uint8_t seq_id,
    48		unsigned char *out_buf
    49	);
    50	
    51	`#endif` // MYSQL_PROTOCOL_UTILS_H

Add bounds checking to prevent silent header truncation for oversized payloads.

payload_len accepts a 32-bit value but the MySQL packet header encodes only the lower 24 bits. Calling mysql_build_packet() with payload_len > 0xFFFFFF silently truncates the header while copying the full payload, corrupting packet boundaries. Additionally, no validation prevents null payload dereferencing when payload_len > 0.

The codebase handles large payloads correctly at higher protocol layers, but this low-level utility lacks input validation. Add bounds checks:

Suggested guard clauses
 size_t mysql_build_packet(
 	const unsigned char *payload,
 	uint32_t payload_len,
 	uint8_t seq_id,
 	unsigned char *out_buf)
 {
+	if (payload_len > 0xFFFFFF) return 0;
+	if (payload_len > 0 && payload == nullptr) return 0;
+
 	// 3-byte length (little-endian) + 1-byte sequence
 	out_buf[0] = payload_len & 0xFF;
 	out_buf[1] = (payload_len >> 8) & 0xFF;
 	out_buf[2] = (payload_len >> 16) & 0xFF;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MySQLProtocolUtils.cpp` around lines 50 - 59, The mysql_build_packet
function currently writes only the lower 24 bits of payload_len into the 3-byte
header and blindly memcpy's payload, which corrupts packets for payload_len >
0xFFFFFF and risks dereferencing a null payload; add guard clauses at the start
of mysql_build_packet to (1) validate payload_len is <= 0xFFFFFF and
reject/return an error code (e.g., -1) if it is larger, and (2) ensure that if
payload_len > 0 the payload pointer is non-null and otherwise return an error;
do not perform the header write or memcpy when the checks fail so out_buf,
seq_id, and memcpy are only used after validation.

Signed-off-by: René Cannaò <rene@proxysql.com>
@renecannao renecannao merged commit 660ed5a into v3.0-5473 Mar 22, 2026
1 of 4 checks passed
@sonarqubecloud
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions
3 Security Hotspots

See analysis details on SonarQube Cloud

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants