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

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

51 statements  

1import io 

2from pathlib import Path 

3from typing import Optional 

4 

5from structlog import get_logger 

6 

7from unblob.extractor import carve_chunk_to_file 

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

9from unblob.models import ( 

10 Chunk, 

11 Extractor, 

12 HandlerDoc, 

13 HandlerType, 

14 HexString, 

15 Reference, 

16 StructHandler, 

17 ValidChunk, 

18) 

19 

20logger = get_logger() 

21 

22C_DEFINITIONS = r""" 

23 typedef struct bdl_toc_entry { 

24 uint64 offset; 

25 uint64 size; 

26 } bdl_toc_entry_t; 

27 

28 typedef struct bdl_header { 

29 char magic[4]; 

30 uint16 major; 

31 uint16 minor; 

32 uint32 toc_offset; 

33 char unknown[4]; 

34 uint32 toc_entries; 

35 uint32 unknowns_2[3]; 

36 char release[256]; 

37 char brand[256]; 

38 char device_id[256]; 

39 char unknown_3[9]; 

40 char version[256]; 

41 char revision[256]; 

42 } bdl_header_t; 

43""" 

44 

45 

46def is_valid_header(header) -> bool: 

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

48 return False 

49 try: 

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

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

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

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

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

55 except UnicodeDecodeError: 

56 return False 

57 return True 

58 

59 

60class HPBDLExtractor(Extractor): 

61 def __init__(self): 

62 self._struct_parser = StructParser(C_DEFINITIONS) 

63 

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

65 entries = [] 

66 with File.from_path(inpath) as file: 

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

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

69 for i in range(header.toc_entries): 

70 entry = self._struct_parser.parse( 

71 "bdl_toc_entry_t", file, Endian.LITTLE 

72 ) 

73 entries.append( 

74 ( 

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

76 Chunk( 

77 start_offset=entry.offset, 

78 end_offset=entry.offset + entry.size, 

79 ), 

80 ) 

81 ) 

82 

83 for carve_path, chunk in entries: 

84 carve_chunk_to_file( 

85 file=file, 

86 chunk=chunk, 

87 carve_path=carve_path, 

88 ) 

89 

90 

91class HPBDLHandler(StructHandler): 

92 NAME = "bdl" 

93 

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

95 

96 C_DEFINITIONS = C_DEFINITIONS 

97 HEADER_STRUCT = "bdl_header_t" 

98 EXTRACTOR = HPBDLExtractor() 

99 

100 DOC = HandlerDoc( 

101 name="HP BDL", 

102 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.", 

103 handler_type=HandlerType.ARCHIVE, 

104 vendor="HP", 

105 references=[ 

106 Reference( 

107 title="hpbdl", 

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

109 ) 

110 ], 

111 limitations=[], 

112 ) 

113 

114 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]: 

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

116 

117 if not is_valid_header(header): 

118 raise InvalidInputFormat("Invalid BDL header.") 

119 

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

121 end_offset = -1 

122 for _ in range(header.toc_entries): 

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

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

125 

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