├── .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 | 14 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 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('