@wireio/protoc-gen-solana 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle/protoc-gen-solana.mjs +7848 -0
- package/dist/bundle/protoc-gen-solana.mjs.map +7 -0
- package/lib/generator/field.d.ts +33 -0
- package/lib/generator/field.d.ts.map +1 -0
- package/lib/generator/field.js +294 -0
- package/lib/generator/index.d.ts +6 -0
- package/lib/generator/index.d.ts.map +1 -0
- package/lib/generator/index.js +4 -0
- package/lib/generator/message.d.ts +23 -0
- package/lib/generator/message.d.ts.map +1 -0
- package/lib/generator/message.js +150 -0
- package/lib/generator/runtime.d.ts +9 -0
- package/lib/generator/runtime.d.ts.map +1 -0
- package/lib/generator/runtime.js +14 -0
- package/lib/generator/type-map.d.ts +54 -0
- package/lib/generator/type-map.d.ts.map +1 -0
- package/lib/generator/type-map.js +221 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +38 -0
- package/lib/plugin.d.ts +12 -0
- package/lib/plugin.d.ts.map +1 -0
- package/lib/plugin.js +173 -0
- package/lib/util/logger.d.ts +9 -0
- package/lib/util/logger.d.ts.map +1 -0
- package/lib/util/logger.js +22 -0
- package/lib/util/names.d.ts +19 -0
- package/lib/util/names.d.ts.map +1 -0
- package/lib/util/names.js +38 -0
- package/package.json +49 -0
- package/rs/protobuf_runtime.rs +352 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a protobuf fully-qualified name to a Rust-safe identifier (PascalCase struct name).
|
|
3
|
+
* e.g. "my_package.MyMessage" → "MyMessage"
|
|
4
|
+
*/
|
|
5
|
+
export declare function protoNameToRust(fqn: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Keep snake_case field name as-is for Rust struct members.
|
|
8
|
+
* e.g. "user_name" → "user_name"
|
|
9
|
+
* Only converts camelCase → snake_case if needed.
|
|
10
|
+
*/
|
|
11
|
+
export declare function toSnakeCase(name: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate output .rs filename for a given .proto file, optionally rooted
|
|
14
|
+
* under a directory derived from the proto package name.
|
|
15
|
+
* e.g. "my_service.proto" with package "example.nested"
|
|
16
|
+
* → "example/nested/my_service.pb.rs"
|
|
17
|
+
*/
|
|
18
|
+
export declare function protoFileToRsFile(protoFile: string, packageName?: string): string;
|
|
19
|
+
//# sourceMappingURL=names.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"names.d.ts","sourceRoot":"","sources":["../../src/util/names.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGhD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,MAAM,CAYjF"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a protobuf fully-qualified name to a Rust-safe identifier (PascalCase struct name).
|
|
3
|
+
* e.g. "my_package.MyMessage" → "MyMessage"
|
|
4
|
+
*/
|
|
5
|
+
export function protoNameToRust(fqn) {
|
|
6
|
+
const parts = fqn.split(".");
|
|
7
|
+
return parts[parts.length - 1];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Keep snake_case field name as-is for Rust struct members.
|
|
11
|
+
* e.g. "user_name" → "user_name"
|
|
12
|
+
* Only converts camelCase → snake_case if needed.
|
|
13
|
+
*/
|
|
14
|
+
export function toSnakeCase(name) {
|
|
15
|
+
// Already snake_case in proto, but handle camelCase just in case
|
|
16
|
+
return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate output .rs filename for a given .proto file, optionally rooted
|
|
20
|
+
* under a directory derived from the proto package name.
|
|
21
|
+
* e.g. "my_service.proto" with package "example.nested"
|
|
22
|
+
* → "example/nested/my_service.pb.rs"
|
|
23
|
+
*/
|
|
24
|
+
export function protoFileToRsFile(protoFile, packageName) {
|
|
25
|
+
const base = protoFile.replace(/\.proto$/, "");
|
|
26
|
+
const parts = base.split("/");
|
|
27
|
+
const filename = parts[parts.length - 1];
|
|
28
|
+
// Rust files use snake_case
|
|
29
|
+
const snakeFilename = filename
|
|
30
|
+
.replace(/([a-z])([A-Z])/g, "$1_$2")
|
|
31
|
+
.toLowerCase();
|
|
32
|
+
const rsBasename = `${snakeFilename}.pb.rs`;
|
|
33
|
+
if (!packageName)
|
|
34
|
+
return rsBasename;
|
|
35
|
+
const dir = packageName.split(".").join("/");
|
|
36
|
+
return `${dir}/${rsBasename}`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmFtZXMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdXRpbC9uYW1lcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsZUFBZSxDQUFDLEdBQVc7SUFDekMsTUFBTSxLQUFLLEdBQUcsR0FBRyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM1QixPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsQ0FBQyxDQUFBO0FBQ2hDLENBQUM7QUFFRDs7OztHQUlHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxJQUFZO0lBQ3RDLGlFQUFpRTtJQUNqRSxPQUFPLElBQUksQ0FBQyxPQUFPLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDLENBQUMsV0FBVyxFQUFFLENBQUE7QUFDL0QsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLFNBQWlCLEVBQUUsV0FBb0I7SUFDdkUsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLENBQUE7SUFDOUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM3QixNQUFNLFFBQVEsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQTtJQUN4Qyw0QkFBNEI7SUFDNUIsTUFBTSxhQUFhLEdBQUcsUUFBUTtTQUMzQixPQUFPLENBQUMsaUJBQWlCLEVBQUUsT0FBTyxDQUFDO1NBQ25DLFdBQVcsRUFBRSxDQUFBO0lBQ2hCLE1BQU0sVUFBVSxHQUFHLEdBQUcsYUFBYSxRQUFRLENBQUE7SUFDM0MsSUFBSSxDQUFDLFdBQVc7UUFBRSxPQUFPLFVBQVUsQ0FBQTtJQUNuQyxNQUFNLEdBQUcsR0FBRyxXQUFXLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUM1QyxPQUFPLEdBQUcsR0FBRyxJQUFJLFVBQVUsRUFBRSxDQUFBO0FBQy9CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENvbnZlcnQgYSBwcm90b2J1ZiBmdWxseS1xdWFsaWZpZWQgbmFtZSB0byBhIFJ1c3Qtc2FmZSBpZGVudGlmaWVyIChQYXNjYWxDYXNlIHN0cnVjdCBuYW1lKS5cbiAqIGUuZy4gXCJteV9wYWNrYWdlLk15TWVzc2FnZVwiIOKGkiBcIk15TWVzc2FnZVwiXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBwcm90b05hbWVUb1J1c3QoZnFuOiBzdHJpbmcpOiBzdHJpbmcge1xuICBjb25zdCBwYXJ0cyA9IGZxbi5zcGxpdChcIi5cIilcbiAgcmV0dXJuIHBhcnRzW3BhcnRzLmxlbmd0aCAtIDFdXG59XG5cbi8qKlxuICogS2VlcCBzbmFrZV9jYXNlIGZpZWxkIG5hbWUgYXMtaXMgZm9yIFJ1c3Qgc3RydWN0IG1lbWJlcnMuXG4gKiBlLmcuIFwidXNlcl9uYW1lXCIg4oaSIFwidXNlcl9uYW1lXCJcbiAqIE9ubHkgY29udmVydHMgY2FtZWxDYXNlIOKGkiBzbmFrZV9jYXNlIGlmIG5lZWRlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvU25ha2VDYXNlKG5hbWU6IHN0cmluZyk6IHN0cmluZyB7XG4gIC8vIEFscmVhZHkgc25ha2VfY2FzZSBpbiBwcm90bywgYnV0IGhhbmRsZSBjYW1lbENhc2UganVzdCBpbiBjYXNlXG4gIHJldHVybiBuYW1lLnJlcGxhY2UoLyhbYS16XSkoW0EtWl0pL2csIFwiJDFfJDJcIikudG9Mb3dlckNhc2UoKVxufVxuXG4vKipcbiAqIEdlbmVyYXRlIG91dHB1dCAucnMgZmlsZW5hbWUgZm9yIGEgZ2l2ZW4gLnByb3RvIGZpbGUsIG9wdGlvbmFsbHkgcm9vdGVkXG4gKiB1bmRlciBhIGRpcmVjdG9yeSBkZXJpdmVkIGZyb20gdGhlIHByb3RvIHBhY2thZ2UgbmFtZS5cbiAqIGUuZy4gXCJteV9zZXJ2aWNlLnByb3RvXCIgd2l0aCBwYWNrYWdlIFwiZXhhbXBsZS5uZXN0ZWRcIlxuICogICAgICDihpIgXCJleGFtcGxlL25lc3RlZC9teV9zZXJ2aWNlLnBiLnJzXCJcbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb3RvRmlsZVRvUnNGaWxlKHByb3RvRmlsZTogc3RyaW5nLCBwYWNrYWdlTmFtZT86IHN0cmluZyk6IHN0cmluZyB7XG4gIGNvbnN0IGJhc2UgPSBwcm90b0ZpbGUucmVwbGFjZSgvXFwucHJvdG8kLywgXCJcIilcbiAgY29uc3QgcGFydHMgPSBiYXNlLnNwbGl0KFwiL1wiKVxuICBjb25zdCBmaWxlbmFtZSA9IHBhcnRzW3BhcnRzLmxlbmd0aCAtIDFdXG4gIC8vIFJ1c3QgZmlsZXMgdXNlIHNuYWtlX2Nhc2VcbiAgY29uc3Qgc25ha2VGaWxlbmFtZSA9IGZpbGVuYW1lXG4gICAgLnJlcGxhY2UoLyhbYS16XSkoW0EtWl0pL2csIFwiJDFfJDJcIilcbiAgICAudG9Mb3dlckNhc2UoKVxuICBjb25zdCByc0Jhc2VuYW1lID0gYCR7c25ha2VGaWxlbmFtZX0ucGIucnNgXG4gIGlmICghcGFja2FnZU5hbWUpIHJldHVybiByc0Jhc2VuYW1lXG4gIGNvbnN0IGRpciA9IHBhY2thZ2VOYW1lLnNwbGl0KFwiLlwiKS5qb2luKFwiL1wiKVxuICByZXR1cm4gYCR7ZGlyfS8ke3JzQmFzZW5hbWV9YFxufVxuIl19
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wireio/protoc-gen-solana",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "protoc plugin generating Rust encode/decode modules from protobuf3 definitions for Solana programs",
|
|
5
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"types": "lib/index.d.ts",
|
|
8
|
+
"module": "dist/bundle/protoc-gen-solana.mjs",
|
|
9
|
+
"bin": {
|
|
10
|
+
"protoc-gen-solana": "dist/bundle/protoc-gen-solana.mjs"
|
|
11
|
+
},
|
|
12
|
+
"pkg": {
|
|
13
|
+
"assets": [
|
|
14
|
+
"rs/**/*"
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"lib",
|
|
19
|
+
"dist/bundle",
|
|
20
|
+
"rs",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"protobufjs": "^8.0.0",
|
|
25
|
+
"tracer": "^1.3.0"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/node": "^25.3.0",
|
|
29
|
+
"@yao-pkg/pkg": "^6.14.1",
|
|
30
|
+
"esbuild": "^0.27.3",
|
|
31
|
+
"prettier": "^3.8.1",
|
|
32
|
+
"protoc": "^29.6.0",
|
|
33
|
+
"typescript": "^5.9.3"
|
|
34
|
+
},
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=24"
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build": "tsc -b tsconfig.json",
|
|
40
|
+
"build:dev": "tsc -b tsconfig.json -w",
|
|
41
|
+
"bundle": "node esbuild.config.js",
|
|
42
|
+
"bundle:dev": "node esbuild.config.js -w",
|
|
43
|
+
"dev": "concurrently npm:build:dev npm:bundle:dev",
|
|
44
|
+
"dist": "pnpm build && pnpm bundle && pkg -c package.json --output dist/bin/protoc-gen-solana -d --options experimental-require-module dist/bundle/protoc-gen-solana.mjs",
|
|
45
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
46
|
+
"generate:test": "npm run dist && mkdir -p ./dist/tests/generated && npx protoc --plugin=protoc-gen-solana=$PWD/dist/bin/protoc-gen-solana --solana_out=./dist/tests/generated tests/protos/*.proto",
|
|
47
|
+
"clean": "rm -rf lib dist"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Shared protobuf3 wire format primitives for protoc-gen-solana.
|
|
2
|
+
// This file is also emitted by the plugin alongside generated codecs.
|
|
3
|
+
//
|
|
4
|
+
// Optimized for Solana's compute budget: minimal allocations,
|
|
5
|
+
// no unnecessary copies, and efficient varint handling.
|
|
6
|
+
|
|
7
|
+
use std::fmt;
|
|
8
|
+
|
|
9
|
+
// ── Error type ───────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
#[derive(Debug, Clone)]
|
|
12
|
+
pub enum DecodeError {
|
|
13
|
+
BufferOverflow,
|
|
14
|
+
InvalidVarint,
|
|
15
|
+
UnknownWireType(u64),
|
|
16
|
+
InvalidData(&'static str),
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
impl fmt::Display for DecodeError {
|
|
20
|
+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
21
|
+
match self {
|
|
22
|
+
DecodeError::BufferOverflow => write!(f, "protobuf: buffer overflow"),
|
|
23
|
+
DecodeError::InvalidVarint => write!(f, "protobuf: invalid varint"),
|
|
24
|
+
DecodeError::UnknownWireType(wt) => write!(f, "protobuf: unknown wire type {}", wt),
|
|
25
|
+
DecodeError::InvalidData(msg) => write!(f, "protobuf: {}", msg),
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Key (tag) encode / decode ────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
#[inline]
|
|
33
|
+
pub fn encode_key(buf: &mut Vec<u8>, tag: u64) {
|
|
34
|
+
encode_varint(buf, tag);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
#[inline]
|
|
38
|
+
pub fn decode_key(data: &[u8], pos: usize) -> Result<(u64, usize), DecodeError> {
|
|
39
|
+
decode_varint(data, pos)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Wire Type 0: Varint ──────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
#[inline]
|
|
45
|
+
pub fn encode_varint(buf: &mut Vec<u8>, mut value: u64) {
|
|
46
|
+
loop {
|
|
47
|
+
if value < 0x80 {
|
|
48
|
+
buf.push(value as u8);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
buf.push(((value & 0x7F) | 0x80) as u8);
|
|
52
|
+
value >>= 7;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#[inline]
|
|
57
|
+
pub fn decode_varint(data: &[u8], mut pos: usize) -> Result<(u64, usize), DecodeError> {
|
|
58
|
+
let mut result: u64 = 0;
|
|
59
|
+
let mut shift: u32 = 0;
|
|
60
|
+
loop {
|
|
61
|
+
if pos >= data.len() {
|
|
62
|
+
return Err(DecodeError::BufferOverflow);
|
|
63
|
+
}
|
|
64
|
+
let b = data[pos];
|
|
65
|
+
pos += 1;
|
|
66
|
+
result |= ((b & 0x7F) as u64) << shift;
|
|
67
|
+
if b & 0x80 == 0 {
|
|
68
|
+
return Ok((result, pos));
|
|
69
|
+
}
|
|
70
|
+
shift += 7;
|
|
71
|
+
if shift > 63 {
|
|
72
|
+
return Err(DecodeError::InvalidVarint);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ── Bool ─────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
#[inline]
|
|
80
|
+
pub fn encode_bool(buf: &mut Vec<u8>, value: bool) {
|
|
81
|
+
buf.push(if value { 1 } else { 0 });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[inline]
|
|
85
|
+
pub fn decode_bool(data: &[u8], pos: usize) -> Result<(bool, usize), DecodeError> {
|
|
86
|
+
let (v, new_pos) = decode_varint(data, pos)?;
|
|
87
|
+
Ok((v != 0, new_pos))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── ZigZag (sint32/sint64) ───────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
#[inline]
|
|
93
|
+
pub fn encode_zigzag32(buf: &mut Vec<u8>, value: i32) {
|
|
94
|
+
let encoded = ((value << 1) ^ (value >> 31)) as u32;
|
|
95
|
+
encode_varint(buf, encoded as u64);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[inline]
|
|
99
|
+
pub fn decode_zigzag32(data: &[u8], pos: usize) -> Result<(i32, usize), DecodeError> {
|
|
100
|
+
let (raw, new_pos) = decode_varint(data, pos)?;
|
|
101
|
+
let n = raw as u32;
|
|
102
|
+
let value = ((n >> 1) as i32) ^ (-((n & 1) as i32));
|
|
103
|
+
Ok((value, new_pos))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
#[inline]
|
|
107
|
+
pub fn encode_zigzag64(buf: &mut Vec<u8>, value: i64) {
|
|
108
|
+
let encoded = ((value << 1) ^ (value >> 63)) as u64;
|
|
109
|
+
encode_varint(buf, encoded);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
#[inline]
|
|
113
|
+
pub fn decode_zigzag64(data: &[u8], pos: usize) -> Result<(i64, usize), DecodeError> {
|
|
114
|
+
let (raw, new_pos) = decode_varint(data, pos)?;
|
|
115
|
+
let value = ((raw >> 1) as i64) ^ (-((raw & 1) as i64));
|
|
116
|
+
Ok((value, new_pos))
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Wire Type 1: 64-bit (little-endian) ─────────────────────────────
|
|
120
|
+
|
|
121
|
+
#[inline]
|
|
122
|
+
pub fn encode_fixed64(buf: &mut Vec<u8>, value: u64) {
|
|
123
|
+
buf.extend_from_slice(&value.to_le_bytes());
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
#[inline]
|
|
127
|
+
pub fn decode_fixed64(data: &[u8], pos: usize) -> Result<(u64, usize), DecodeError> {
|
|
128
|
+
if pos + 8 > data.len() {
|
|
129
|
+
return Err(DecodeError::BufferOverflow);
|
|
130
|
+
}
|
|
131
|
+
let value = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
|
|
132
|
+
Ok((value, pos + 8))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[inline]
|
|
136
|
+
pub fn encode_sfixed64(buf: &mut Vec<u8>, value: i64) {
|
|
137
|
+
encode_fixed64(buf, value as u64);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[inline]
|
|
141
|
+
pub fn decode_sfixed64(data: &[u8], pos: usize) -> Result<(i64, usize), DecodeError> {
|
|
142
|
+
let (raw, new_pos) = decode_fixed64(data, pos)?;
|
|
143
|
+
Ok((raw as i64, new_pos))
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ── Wire Type 5: 32-bit (little-endian) ─────────────────────────────
|
|
147
|
+
|
|
148
|
+
#[inline]
|
|
149
|
+
pub fn encode_fixed32(buf: &mut Vec<u8>, value: u32) {
|
|
150
|
+
buf.extend_from_slice(&value.to_le_bytes());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[inline]
|
|
154
|
+
pub fn decode_fixed32(data: &[u8], pos: usize) -> Result<(u32, usize), DecodeError> {
|
|
155
|
+
if pos + 4 > data.len() {
|
|
156
|
+
return Err(DecodeError::BufferOverflow);
|
|
157
|
+
}
|
|
158
|
+
let value = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
|
|
159
|
+
Ok((value, pos + 4))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
#[inline]
|
|
163
|
+
pub fn encode_sfixed32(buf: &mut Vec<u8>, value: i32) {
|
|
164
|
+
encode_fixed32(buf, value as u32);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[inline]
|
|
168
|
+
pub fn decode_sfixed32(data: &[u8], pos: usize) -> Result<(i32, usize), DecodeError> {
|
|
169
|
+
let (raw, new_pos) = decode_fixed32(data, pos)?;
|
|
170
|
+
Ok((raw as i32, new_pos))
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Wire Type 2: Length-delimited ────────────────────────────────────
|
|
174
|
+
|
|
175
|
+
#[inline]
|
|
176
|
+
pub fn encode_bytes(buf: &mut Vec<u8>, value: &[u8]) {
|
|
177
|
+
encode_varint(buf, value.len() as u64);
|
|
178
|
+
buf.extend_from_slice(value);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
#[inline]
|
|
182
|
+
pub fn decode_bytes(data: &[u8], pos: usize) -> Result<(Vec<u8>, usize), DecodeError> {
|
|
183
|
+
let (len, pos) = decode_varint(data, pos)?;
|
|
184
|
+
let len = len as usize;
|
|
185
|
+
if pos + len > data.len() {
|
|
186
|
+
return Err(DecodeError::BufferOverflow);
|
|
187
|
+
}
|
|
188
|
+
Ok((data[pos..pos + len].to_vec(), pos + len))
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
#[inline]
|
|
192
|
+
pub fn encode_string(buf: &mut Vec<u8>, value: &str) {
|
|
193
|
+
encode_bytes(buf, value.as_bytes());
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[inline]
|
|
197
|
+
pub fn decode_string(data: &[u8], pos: usize) -> Result<(String, usize), DecodeError> {
|
|
198
|
+
let (raw, new_pos) = decode_bytes(data, pos)?;
|
|
199
|
+
String::from_utf8(raw)
|
|
200
|
+
.map(|s| (s, new_pos))
|
|
201
|
+
.map_err(|_| DecodeError::InvalidData("invalid UTF-8 in string field"))
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ── Skip unknown fields ──────────────────────────────────────────────
|
|
205
|
+
|
|
206
|
+
#[inline]
|
|
207
|
+
pub fn skip_field(data: &[u8], pos: usize, wire_type: u64) -> Result<usize, DecodeError> {
|
|
208
|
+
match wire_type {
|
|
209
|
+
0 => {
|
|
210
|
+
// Varint: skip until MSB is clear
|
|
211
|
+
let (_, new_pos) = decode_varint(data, pos)?;
|
|
212
|
+
Ok(new_pos)
|
|
213
|
+
}
|
|
214
|
+
1 => {
|
|
215
|
+
// 64-bit: skip 8 bytes
|
|
216
|
+
if pos + 8 > data.len() {
|
|
217
|
+
return Err(DecodeError::BufferOverflow);
|
|
218
|
+
}
|
|
219
|
+
Ok(pos + 8)
|
|
220
|
+
}
|
|
221
|
+
2 => {
|
|
222
|
+
// Length-delimited: read length, skip that many bytes
|
|
223
|
+
let (len, new_pos) = decode_varint(data, pos)?;
|
|
224
|
+
let end = new_pos + len as usize;
|
|
225
|
+
if end > data.len() {
|
|
226
|
+
return Err(DecodeError::BufferOverflow);
|
|
227
|
+
}
|
|
228
|
+
Ok(end)
|
|
229
|
+
}
|
|
230
|
+
5 => {
|
|
231
|
+
// 32-bit: skip 4 bytes
|
|
232
|
+
if pos + 4 > data.len() {
|
|
233
|
+
return Err(DecodeError::BufferOverflow);
|
|
234
|
+
}
|
|
235
|
+
Ok(pos + 4)
|
|
236
|
+
}
|
|
237
|
+
_ => Err(DecodeError::UnknownWireType(wire_type)),
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[cfg(test)]
|
|
242
|
+
mod tests {
|
|
243
|
+
use super::*;
|
|
244
|
+
|
|
245
|
+
#[test]
|
|
246
|
+
fn test_varint_roundtrip() {
|
|
247
|
+
for &val in &[0u64, 1, 127, 128, 255, 300, 16384, u64::MAX] {
|
|
248
|
+
let mut buf = Vec::new();
|
|
249
|
+
encode_varint(&mut buf, val);
|
|
250
|
+
let (decoded, pos) = decode_varint(&buf, 0).unwrap();
|
|
251
|
+
assert_eq!(decoded, val);
|
|
252
|
+
assert_eq!(pos, buf.len());
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn test_bool_roundtrip() {
|
|
258
|
+
for &val in &[true, false] {
|
|
259
|
+
let mut buf = Vec::new();
|
|
260
|
+
encode_bool(&mut buf, val);
|
|
261
|
+
let (decoded, _) = decode_bool(&buf, 0).unwrap();
|
|
262
|
+
assert_eq!(decoded, val);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
#[test]
|
|
267
|
+
fn test_zigzag32_roundtrip() {
|
|
268
|
+
for &val in &[0i32, 1, -1, 2, -2, i32::MAX, i32::MIN] {
|
|
269
|
+
let mut buf = Vec::new();
|
|
270
|
+
encode_zigzag32(&mut buf, val);
|
|
271
|
+
let (decoded, _) = decode_zigzag32(&buf, 0).unwrap();
|
|
272
|
+
assert_eq!(decoded, val);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
#[test]
|
|
277
|
+
fn test_zigzag64_roundtrip() {
|
|
278
|
+
for &val in &[0i64, 1, -1, 2, -2, i64::MAX, i64::MIN] {
|
|
279
|
+
let mut buf = Vec::new();
|
|
280
|
+
encode_zigzag64(&mut buf, val);
|
|
281
|
+
let (decoded, _) = decode_zigzag64(&buf, 0).unwrap();
|
|
282
|
+
assert_eq!(decoded, val);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#[test]
|
|
287
|
+
fn test_fixed64_roundtrip() {
|
|
288
|
+
for &val in &[0u64, 1, 0xDEAD_BEEF, u64::MAX] {
|
|
289
|
+
let mut buf = Vec::new();
|
|
290
|
+
encode_fixed64(&mut buf, val);
|
|
291
|
+
let (decoded, _) = decode_fixed64(&buf, 0).unwrap();
|
|
292
|
+
assert_eq!(decoded, val);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#[test]
|
|
297
|
+
fn test_fixed32_roundtrip() {
|
|
298
|
+
for &val in &[0u32, 1, 0xDEAD, u32::MAX] {
|
|
299
|
+
let mut buf = Vec::new();
|
|
300
|
+
encode_fixed32(&mut buf, val);
|
|
301
|
+
let (decoded, _) = decode_fixed32(&buf, 0).unwrap();
|
|
302
|
+
assert_eq!(decoded, val);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#[test]
|
|
307
|
+
fn test_string_roundtrip() {
|
|
308
|
+
for val in &["", "hello", "hello world 🌍"] {
|
|
309
|
+
let mut buf = Vec::new();
|
|
310
|
+
encode_string(&mut buf, val);
|
|
311
|
+
let (decoded, _) = decode_string(&buf, 0).unwrap();
|
|
312
|
+
assert_eq!(&decoded, val);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
#[test]
|
|
317
|
+
fn test_bytes_roundtrip() {
|
|
318
|
+
for val in &[vec![], vec![1u8, 2, 3], vec![0xFF; 300]] {
|
|
319
|
+
let mut buf = Vec::new();
|
|
320
|
+
encode_bytes(&mut buf, val);
|
|
321
|
+
let (decoded, _) = decode_bytes(&buf, 0).unwrap();
|
|
322
|
+
assert_eq!(&decoded, val);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
#[test]
|
|
327
|
+
fn test_skip_field() {
|
|
328
|
+
// Varint
|
|
329
|
+
let mut buf = Vec::new();
|
|
330
|
+
encode_varint(&mut buf, 300);
|
|
331
|
+
let new_pos = skip_field(&buf, 0, 0).unwrap();
|
|
332
|
+
assert_eq!(new_pos, buf.len());
|
|
333
|
+
|
|
334
|
+
// Fixed64
|
|
335
|
+
let mut buf = Vec::new();
|
|
336
|
+
encode_fixed64(&mut buf, 42);
|
|
337
|
+
let new_pos = skip_field(&buf, 0, 1).unwrap();
|
|
338
|
+
assert_eq!(new_pos, 8);
|
|
339
|
+
|
|
340
|
+
// Length-delimited
|
|
341
|
+
let mut buf = Vec::new();
|
|
342
|
+
encode_string(&mut buf, "hello");
|
|
343
|
+
let new_pos = skip_field(&buf, 0, 2).unwrap();
|
|
344
|
+
assert_eq!(new_pos, buf.len());
|
|
345
|
+
|
|
346
|
+
// Fixed32
|
|
347
|
+
let mut buf = Vec::new();
|
|
348
|
+
encode_fixed32(&mut buf, 42);
|
|
349
|
+
let new_pos = skip_field(&buf, 0, 5).unwrap();
|
|
350
|
+
assert_eq!(new_pos, 4);
|
|
351
|
+
}
|
|
352
|
+
}
|