Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/hp/bdl.py: 42%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

50 statements  

1import io 

2from pathlib import Path 

3 

4from structlog import get_logger 

5 

6from unblob.extractor import carve_chunk_to_file 

7from unblob.file_utils import Endian, File, InvalidInputFormat, StructParser, snull 

8from unblob.models import ( 

9 Chunk, 

10 Extractor, 

11 HandlerDoc, 

12 HandlerType, 

13 HexString, 

14 Reference, 

15 StructHandler, 

16 ValidChunk, 

17) 

18 

19logger = get_logger() 

20 

21C_DEFINITIONS = r""" 

22 typedef struct bdl_toc_entry { 

23 uint64 offset; 

24 uint64 size; 

25 } bdl_toc_entry_t; 

26 

27 typedef struct bdl_header { 

28 char magic[4]; 

29 uint16 major; 

30 uint16 minor; 

31 uint32 toc_offset; 

32 char unknown[4]; 

33 uint32 toc_entries; 

34 uint32 unknowns_2[3]; 

35 char release[256]; 

36 char brand[256]; 

37 char device_id[256]; 

38 char unknown_3[9]; 

39 char version[256]; 

40 char revision[256]; 

41 } bdl_header_t; 

42""" 

43 

44 

45def is_valid_header(header) -> bool: 

46 if header.toc_offset == 0 or header.toc_entries == 0: 

47 return False 

48 try: 

49 snull(header.release).decode("utf-8") 

50 snull(header.brand).decode("utf-8") 

51 snull(header.device_id).decode("utf-8") 

52 snull(header.version).decode("utf-8") 

53 snull(header.revision).decode("utf-8") 

54 except UnicodeDecodeError: 

55 return False 

56 return True 

57 

58 

59class HPBDLExtractor(Extractor): 

60 def __init__(self): 

61 self._struct_parser = StructParser(C_DEFINITIONS) 

62 

63 def extract(self, inpath: Path, outdir: Path): 

64 entries = [] 

65 with File.from_path(inpath) as file: 

66 header = self._struct_parser.parse("bdl_header_t", file, Endian.LITTLE) 

67 file.seek(header.toc_offset, io.SEEK_SET) 

68 for i in range(header.toc_entries): 

69 entry = self._struct_parser.parse( 

70 "bdl_toc_entry_t", file, Endian.LITTLE 

71 ) 

72 entries.append( 

73 ( 

74 outdir.joinpath(outdir.joinpath(Path(f"ipkg{i:03}"))), 

75 Chunk( 

76 start_offset=entry.offset, 

77 end_offset=entry.offset + entry.size, 

78 ), 

79 ) 

80 ) 

81 

82 for carve_path, chunk in entries: 

83 carve_chunk_to_file( 

84 file=file, 

85 chunk=chunk, 

86 carve_path=carve_path, 

87 ) 

88 

89 

90class HPBDLHandler(StructHandler): 

91 NAME = "bdl" 

92 

93 PATTERNS = [HexString("69 62 64 6C 01 00 01 00")] 

94 

95 C_DEFINITIONS = C_DEFINITIONS 

96 HEADER_STRUCT = "bdl_header_t" 

97 EXTRACTOR = HPBDLExtractor() 

98 

99 DOC = HandlerDoc( 

100 name="HP BDL", 

101 description="The HP BDL format is a firmware archive containing a custom header and a table of contents that specifies offsets and sizes of embedded firmware components. It includes metadata such as release, brand, device ID, version, and revision.", 

102 handler_type=HandlerType.ARCHIVE, 

103 vendor="HP", 

104 references=[ 

105 Reference( 

106 title="hpbdl", 

107 url="https://github.com/tylerwhall/hpbdl", 

108 ) 

109 ], 

110 limitations=[], 

111 ) 

112 

113 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

114 header = self.parse_header(file, endian=Endian.LITTLE) 

115 

116 if not is_valid_header(header): 

117 raise InvalidInputFormat("Invalid BDL header.") 

118 

119 file.seek(start_offset + header.toc_offset, io.SEEK_SET) 

120 end_offset = -1 

121 for _ in range(header.toc_entries): 

122 entry = self._struct_parser.parse("bdl_toc_entry_t", file, Endian.LITTLE) 

123 end_offset = max(end_offset, start_offset + entry.offset + entry.size) 

124 

125 return ValidChunk(start_offset=start_offset, end_offset=end_offset)