├── .idea
├── .gitignore
├── ProtoBuf.iml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
└── modules.xml
├── Demo.py
├── PBDecoder.py
├── PBEncoder.py
└── README.md
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # 默认忽略的文件
2 | /shelf/
3 | /workspace.xml
4 | # 基于编辑器的 HTTP 客户端请求
5 | /httpRequests/
6 | # Datasource local storage ignored files
7 | /dataSources/
8 | /dataSources.local.xml
9 |
--------------------------------------------------------------------------------
/.idea/ProtoBuf.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Demo.py:
--------------------------------------------------------------------------------
1 | from PBDecoder import decodeProto, ProtobufDisplay
2 | from PBEncoder import encodeProto
3 |
4 | if __name__ == '__main__':
5 | ProtoBuf = bytes.fromhex("08C45E1218E5B7B2E59CA8E585B6E4BB96E59CB0E696B9E799BBE5BD95")
6 | result = decodeProto(ProtoBuf)
7 | Data = ProtobufDisplay(result, True) # 如果需要转回ProtoBuf参数2为True,会输出value类型。
8 | print(Data)
9 |
10 | ProtoBuf = encodeProto(Data).hex()
11 | print(ProtoBuf)
12 |
--------------------------------------------------------------------------------
/PBDecoder.py:
--------------------------------------------------------------------------------
1 | import struct
2 |
3 |
4 | class BufferReader:
5 | def __init__(self, buffer):
6 | self.buffer = buffer
7 | self.offset = 0
8 |
9 | def read_varint(self):
10 | value, length = self.decode_varint(self.buffer, self.offset)
11 | self.offset += length
12 | return value
13 |
14 | def read_buffer(self, length):
15 | self.check_byte(length)
16 | result = self.buffer[self.offset:self.offset + length]
17 | self.offset += length
18 | return result
19 |
20 | def try_skip_grpc_header(self):
21 | backup_offset = self.offset
22 |
23 | # 首先检查缓冲区是否为空
24 | if not self.buffer:
25 | return
26 |
27 | # 检查是否有足够的字节
28 | if self.left_bytes() >= 5 and self.buffer[self.offset] == 0:
29 | self.offset += 1
30 |
31 | # 确保有足够的字节读取 4 字节的长度
32 | if self.left_bytes() >= 4:
33 | length = struct.unpack('>I', self.buffer[self.offset:self.offset + 4])[0]
34 | self.offset += 4
35 |
36 | # 检查长度是否合法
37 | if length <= self.left_bytes():
38 | return
39 |
40 | # 如果任何检查失败,恢复到原始偏移量
41 | self.offset = backup_offset
42 |
43 | def left_bytes(self):
44 | return len(self.buffer) - self.offset
45 |
46 | def check_byte(self, length):
47 | bytes_available = self.left_bytes()
48 | if length > bytes_available:
49 | raise ValueError(f"Not enough bytes left. Requested: {length} left: {bytes_available}")
50 |
51 | def checkpoint(self):
52 | self.saved_offset = self.offset
53 |
54 | def reset_to_checkpoint(self):
55 | self.offset = self.saved_offset
56 |
57 | def decode_varint(self, buffer, offset):
58 | value = 0
59 | shift = 0
60 | length = 0
61 |
62 | while True:
63 | byte = buffer[offset]
64 | offset += 1
65 | value |= (byte & 0x7F) << shift
66 | length += 1
67 | if byte & 0x80 == 0:
68 | break
69 | shift += 7
70 |
71 | return value, length
72 |
73 |
74 | TYPES = {
75 | 'VARINT': 0,
76 | 'FIXED64': 1,
77 | 'LENDELIM': 2,
78 | 'FIXED32': 5
79 | }
80 |
81 |
82 | def decodeProto(buffer):
83 | reader = BufferReader(buffer)
84 | parts = []
85 |
86 | reader.try_skip_grpc_header()
87 |
88 | try:
89 | while reader.left_bytes() > 0:
90 | reader.checkpoint()
91 |
92 | byte_range = [reader.offset]
93 | index_type = reader.read_varint()
94 | type_ = index_type & 0b111
95 | index = index_type >> 3
96 |
97 | if type_ == TYPES['VARINT']:
98 | value = reader.read_varint()
99 | elif type_ == TYPES['LENDELIM']:
100 | length = reader.read_varint()
101 | value = reader.read_buffer(length)
102 | elif type_ == TYPES['FIXED32']:
103 |
104 | value = reader.read_buffer(4)
105 | elif type_ == TYPES['FIXED64']:
106 | value = reader.read_buffer(8)
107 | else:
108 | raise ValueError(f"Unknown type: {type_}")
109 |
110 | byte_range.append(reader.offset)
111 |
112 | parts.append({
113 | 'byteRange': byte_range,
114 | 'index': index,
115 | 'type': type_,
116 | 'value': value
117 | })
118 | except Exception as err:
119 | reader.reset_to_checkpoint()
120 |
121 | return {
122 | 'parts': parts,
123 | 'leftOver': reader.read_buffer(reader.left_bytes())
124 | }
125 |
126 |
127 | def type_to_string(type_, sub_type=None):
128 | if type_ == TYPES['VARINT']:
129 | return "varint"
130 | elif type_ == TYPES['LENDELIM']:
131 | return sub_type or "len_delim"
132 | elif type_ == TYPES['FIXED32']:
133 | return "fixed32"
134 | elif type_ == TYPES['FIXED64']:
135 | return "fixed64"
136 | else:
137 | return "unknown"
138 |
139 |
140 | def decoded_to_dict(decoded, IsType=True):
141 | result = {}
142 | # for part in decoded["parts"]:
143 | # index = part["index"]
144 | # value = getProtobufPart(part, IsType)
145 | # result[index] = value
146 | for part in decoded["parts"]:
147 | index = part["index"]
148 | value = getProtobufPart(part, IsType)
149 | if index not in result:
150 | result[index] = value
151 | else:
152 | if isinstance(result[index], list):
153 | result[index].append(value)
154 | else:
155 | result[index] = [result[index], value]
156 |
157 | return result
158 |
159 |
160 | def getProtobufPart(part, IsType=True):
161 | part_type = part["type"]
162 | part_value = part["value"]
163 | if part_type == TYPES["VARINT"]:
164 | if IsType:
165 | return part_type, part_value
166 | return part_value
167 |
168 | elif part_type == TYPES["LENDELIM"]:
169 | decoded = decodeProto(part_value)
170 |
171 | if len(part_value) > 0 and len(decoded["leftOver"]) == 0:
172 | return decoded_to_dict(decoded,IsType)
173 | else:
174 | if IsType:
175 | return part_type, part_value.decode("utf-8")
176 | return part_value.decode("utf-8")
177 | elif part_type == TYPES["FIXED64"]:
178 | part_value = struct.unpack(' 0x7f:
11 | buf += bytes([(value & 0x7f) | 0x80])
12 | value >>= 7
13 | buf += bytes([value])
14 | return buf
15 |
16 | def encode_length_delimited(value):
17 | """Encode a length-delimited field."""
18 | if isinstance(value, str):
19 | value = value.encode()
20 | length_bytes = encode_varint(len(value))
21 | return length_bytes + value
22 |
23 | def encode_field(index, value, wire_type):
24 | """Encode a single field."""
25 | # Combine index and wire type
26 | tag = (index << 3) | wire_type
27 | tag_bytes = encode_varint(tag)
28 |
29 | if wire_type == 0: # VARINT
30 | return tag_bytes + encode_varint(value)
31 | elif wire_type == 2: # LENGTH_DELIMITED
32 | return tag_bytes + encode_length_delimited(value)
33 | elif wire_type == 1: # FIXED64
34 | return tag_bytes + struct.pack('