Skip to content

Commit 5566c7b

Browse files
committed
fru: add support to ignore checksum missmatch
There were several invalid encoded FRU data available and reported several time as issue on github. To enable people ignore that the option 'ignore_checksum' is introduced. See also github issue #186. Signed-off-by: Heiko Thiery <[email protected]>
1 parent d1309a4 commit 5566c7b

File tree

2 files changed

+77
-45
lines changed

2 files changed

+77
-45
lines changed

pyipmi/fru.py

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ def read_fru_data(self, offset=None, count=None, fru_id=0):
8888
def read_fru_data_full(self, fru_id=0):
8989
return self.read_fru_data(fru_id=fru_id)
9090

91-
def get_fru_inventory_header(self, fru_id=0):
91+
def get_fru_inventory_header(self, fru_id=0, ignore_checksum=False):
9292
data = self.read_fru_data(offset=0, count=8, fru_id=fru_id)
93-
return InventoryCommonHeader(data)
93+
return InventoryCommonHeader(data, ignore_checksum=ignore_checksum)
9494

9595
def _read_fru_area(self, offset, fru_id=0):
9696
# read the area header
@@ -99,26 +99,30 @@ def _read_fru_area(self, offset, fru_id=0):
9999
count = data[1] * 8
100100
return self.read_fru_data(offset=offset, count=count, fru_id=fru_id)
101101

102-
def get_fru_chassis_area(self, fru_id=0):
103-
header = self.get_fru_inventory_header(fru_id=fru_id)
102+
def get_fru_chassis_area(self, fru_id=0, ignore_checksum=False):
103+
header = self.get_fru_inventory_header(fru_id=fru_id,
104+
ignore_checksum=ignore_checksum)
104105
data = self._read_fru_area(offset=header.chassis_info_area_offset,
105106
fru_id=fru_id)
106-
return InventoryChassisInfoArea(data)
107+
return InventoryChassisInfoArea(data, ignore_checksum=ignore_checksum)
107108

108-
def get_fru_board_area(self, fru_id=0):
109-
header = self.get_fru_inventory_header(fru_id=fru_id)
109+
def get_fru_board_area(self, fru_id=0, ignore_checksum=False):
110+
header = self.get_fru_inventory_header(fru_id=fru_id,
111+
ignore_checksum=ignore_checksum)
110112
data = self._read_fru_area(offset=header.board_info_area_offset,
111113
fru_id=fru_id)
112-
return InventoryBoardInfoArea(data)
114+
return InventoryBoardInfoArea(data, ignore_checksum=ignore_checksum)
113115

114-
def get_fru_product_area(self, fru_id=0):
115-
header = self.get_fru_inventory_header(fru_id=fru_id)
116+
def get_fru_product_area(self, fru_id=0, ignore_checksum=False):
117+
header = self.get_fru_inventory_header(fru_id=fru_id,
118+
ignore_checksum=ignore_checksum)
116119
data = self._read_fru_area(offset=header.product_info_area_offset,
117120
fru_id=fru_id)
118-
return InventoryProductInfoArea(data)
121+
return InventoryProductInfoArea(data, ignore_checksum=ignore_checksum)
119122

120-
def get_fru_multirecord_area(self, fru_id=0):
121-
header = self.get_fru_inventory_header(fru_id=fru_id)
123+
def get_fru_multirecord_area(self, fru_id=0, ignore_checksum=False):
124+
header = self.get_fru_inventory_header(fru_id=fru_id,
125+
ignore_checksum=ignore_checksum)
122126

123127
# we have to determine the length of the area first
124128
offset = header.multirecord_area_offset
@@ -137,9 +141,9 @@ def get_fru_multirecord_area(self, fru_id=0):
137141
# now read the full area
138142
offset = header.multirecord_area_offset
139143
data = self.read_fru_data(offset=offset, count=count)
140-
return InventoryMultiRecordArea(data)
144+
return InventoryMultiRecordArea(data, ignore_checksum=ignore_checksum)
141145

142-
def get_fru_inventory(self, fru_id=0):
146+
def get_fru_inventory(self, fru_id=0, ignore_checksum=False):
143147
"""
144148
Get the full parsed FRU inventory data.
145149
"""
@@ -161,7 +165,7 @@ def get_fru_inventory(self, fru_id=0):
161165
return fru
162166

163167

164-
def get_fru_inventory_from_file(filename):
168+
def get_fru_inventory_from_file(filename, ignore_checksum=False):
165169
try:
166170
file = open(filename, "rb")
167171
except IOError:
@@ -173,7 +177,7 @@ def get_fru_inventory_from_file(filename):
173177
file_data = file.read(file_size)
174178
data = array.array('B', file_data)
175179
file.close()
176-
return FruInventory(data)
180+
return FruInventory(data, ignore_checksum=ignore_checksum)
177181

178182

179183
CUSTOM_FIELD_END = 0xc1
@@ -190,37 +194,37 @@ def _decode_custom_fields(data):
190194

191195

192196
class FruData(object):
193-
def __init__(self, data=None):
197+
def __init__(self, data=None, ignore_checksum=False):
194198
if data:
195199
if isinstance(data, str):
196200
data = [ord(c) for c in data]
197201
self.data = data
198202
if hasattr(self, '_from_data'):
199-
self._from_data(data)
203+
self._from_data(data, ignore_checksum=ignore_checksum)
200204

201205

202206
class InventoryCommonHeader(FruData):
203-
def _from_data(self, data):
204-
if len(data) != 8:
207+
def _from_data(self, data, ignore_checksum=False):
208+
if len(data) < 8:
205209
raise DecodingError('InventoryCommonHeader length != 8')
206210
self.format_version = data[0] & 0x0f
207211
self.internal_use_area_offset = data[1] * 8 or None
208212
self.chassis_info_area_offset = data[2] * 8 or None
209213
self.board_info_area_offset = data[3] * 8 or None
210214
self.product_info_area_offset = data[4] * 8 or None
211215
self.multirecord_area_offset = data[5] * 8 or None
212-
if sum(data) % 256 != 0:
213-
raise DecodingError('InventoryCommonHeader checksum failed')
216+
if sum(data[:8]) % 256 != 0 and ignore_checksum is False:
217+
raise DecodingError(f'InventoryCommonHeader checksum failed {sum(data) % 0x10}')
214218

215219

216220
class CommonInfoArea(FruData):
217-
def _from_data(self, data):
221+
def _from_data(self, data, ignore_checksum=False):
218222
self.format_version = data[0] & 0x0f
219223
if self.format_version != 1:
220224
raise DecodingError('unsupported format version (%d)' %
221225
self.format_version)
222226
self.length = data[1] * 8
223-
if sum(data[:self.length]) % 256 != 0:
227+
if sum(data[:self.length]) % 256 != 0 and ignore_checksum is False:
224228
raise DecodingError('checksum failed')
225229

226230

@@ -249,7 +253,7 @@ class InventoryChassisInfoArea(CommonInfoArea):
249253
TYPE_RAID_CHASSIS = 22
250254
TYPE_RACK_MOUNT_CHASSIS = 23
251255

252-
def _from_data(self, data):
256+
def _from_data(self, data, ignore_checksum=False):
253257
CommonInfoArea._from_data(self, data)
254258
self.type = data[2]
255259
offset = 3
@@ -261,8 +265,8 @@ def _from_data(self, data):
261265

262266

263267
class InventoryBoardInfoArea(CommonInfoArea):
264-
def _from_data(self, data):
265-
CommonInfoArea._from_data(self, data)
268+
def _from_data(self, data, ignore_checksum=False):
269+
CommonInfoArea._from_data(self, data, ignore_checksum=ignore_checksum)
266270
self.language_code = data[2]
267271
minutes = data[5] << 16 | data[4] << 8 | data[3]
268272
self.mfg_date = (datetime.datetime(1996, 1, 1)
@@ -282,7 +286,7 @@ def _from_data(self, data):
282286

283287

284288
class InventoryProductInfoArea(CommonInfoArea):
285-
def _from_data(self, data):
289+
def _from_data(self, data, ignore_checksum=False):
286290
CommonInfoArea._from_data(self, data)
287291
self.language_code = data[2]
288292
offset = 3
@@ -318,17 +322,17 @@ def __str__(self):
318322
return '%02x: %s' % (self.record_type_id,
319323
' '.join('%02x' % b for b in self.raw))
320324

321-
def _from_data(self, data):
325+
def _from_data(self, data, ignore_checksum=False):
322326
if len(data) < 5:
323327
raise DecodingError('data too short')
324328
self.record_type_id = data[0]
325329
self.format_version = data[1] & 0x0f
326330
self.end_of_list = bool(data[1] & 0x80)
327331
self.length = data[2]
328-
if sum(data[:5]) % 256 != 0:
332+
if sum(data[:5]) % 256 != 0 and ignore_checksum is False:
329333
raise DecodingError('FruDataMultiRecord header checksum failed')
330334
self.raw = data[5:5+self.length]
331-
if (sum(self.raw) + data[3]) % 256 != 0:
335+
if (sum(self.raw) + data[3]) % 256 != 0 and ignore_checksum is False:
332336
raise DecodingError('FruDataMultiRecord record checksum failed')
333337

334338
@staticmethod
@@ -386,19 +390,19 @@ def create_from_record_id(data):
386390

387391
return FruPicmgRecord(data)
388392

389-
def _from_data(self, data):
393+
def _from_data(self, data, ignore_checksum=False):
390394
if len(data) < 10:
391395
raise DecodingError('data too short')
392396
data = array.array('B', data)
393-
FruDataMultiRecord._from_data(self, data)
397+
FruDataMultiRecord._from_data(self, data, ignore_checksum=ignore_checksum)
394398
self.manufacturer_id = \
395399
data[5] | data[6] << 8 | data[7] << 16
396400
self.picmg_record_type_id = data[8]
397401
self.format_version = data[9]
398402

399403

400404
class FruPicmgPowerModuleCapabilityRecord(FruPicmgRecord):
401-
def _from_data(self, data):
405+
def _from_data(self, data, ignore_checksum=False):
402406
if len(data) < 12:
403407
raise DecodingError('data too short')
404408
FruPicmgRecord._from_data(self, data)
@@ -407,11 +411,11 @@ def _from_data(self, data):
407411

408412

409413
class InventoryMultiRecordArea(object):
410-
def __init__(self, data):
414+
def __init__(self, data, ignore_checksum=False):
411415
if data:
412416
self._from_data(data)
413417

414-
def _from_data(self, data):
418+
def _from_data(self, data, ignore_checksum=False):
415419
self.records = list()
416420
offset = 0
417421
while True:
@@ -423,31 +427,35 @@ def _from_data(self, data):
423427

424428

425429
class FruInventory(object):
426-
def __init__(self, data=None):
430+
def __init__(self, data=None, ignore_checksum=False):
427431
self.chassis_info_area = None
428432
self.board_info_area = None
429433
self.product_info_area = None
430434
self.multirecord_area = None
431435

432436
if data:
433-
self._from_data(data)
437+
self._from_data(data, ignore_checksum=ignore_checksum)
434438

435-
def _from_data(self, data):
439+
def _from_data(self, data, ignore_checksum=False):
436440
self.raw = data
437441
self.common_header = InventoryCommonHeader(data[:8])
438442

439443
if self.common_header.chassis_info_area_offset:
440444
self.chassis_info_area = InventoryChassisInfoArea(
441-
data[self.common_header.chassis_info_area_offset:])
445+
data[self.common_header.chassis_info_area_offset:],
446+
ignore_checksum=ignore_checksum)
442447

443448
if self.common_header.board_info_area_offset:
444449
self.board_info_area = InventoryBoardInfoArea(
445-
data[self.common_header.board_info_area_offset:])
450+
data[self.common_header.board_info_area_offset:],
451+
ignore_checksum=ignore_checksum)
446452

447453
if self.common_header.product_info_area_offset:
448454
self.product_info_area = InventoryProductInfoArea(
449-
data[self.common_header.product_info_area_offset:])
455+
data[self.common_header.product_info_area_offset:],
456+
ignore_checksum=ignore_checksum)
450457

451458
if self.common_header.multirecord_area_offset:
452459
self.multirecord_area = InventoryMultiRecordArea(
453-
data[self.common_header.multirecord_area_offset:])
460+
data[self.common_header.multirecord_area_offset:],
461+
ignore_checksum=ignore_checksum)

tests/test_fru.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
11
#!/usr/bin/env python
22
# -*- coding: utf-8 -*-
33
import os
4+
import pytest
45

5-
from pyipmi.fru import (FruData, FruPicmgPowerModuleCapabilityRecord,
6+
from pyipmi.errors import (DecodingError)
7+
8+
from pyipmi.fru import (FruData, FruInventory,
9+
FruPicmgPowerModuleCapabilityRecord,
610
InventoryCommonHeader, InventoryBoardInfoArea,
711
get_fru_inventory_from_file)
812

913

1014
this_file_path = os.path.dirname(os.path.abspath(__file__))
1115

16+
1217
def test_frudata_object():
1318
fru_field = FruData((0, 1, 2, 3))
1419
assert fru_field.data[0] == 0
@@ -27,6 +32,13 @@ def test_commonheader_object():
2732
InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 235))
2833

2934

35+
def test_commonheader_object_invalid_checksum():
36+
with pytest.raises(DecodingError):
37+
InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0))
38+
39+
InventoryCommonHeader((0, 1, 2, 3, 4, 5, 6, 0), ignore_checksum=True)
40+
41+
3042
def test_fru_inventory_from_file():
3143
fru_file = os.path.join(this_file_path, 'fru_bin/kontron_am4010.bin')
3244
fru = get_fru_inventory_from_file(fru_file)
@@ -70,3 +82,15 @@ def test_BoardInfoArea():
7082
assert area.product_name.string == 'PowerEdge R515 '
7183
assert area.serial_number.string == 'CN717033AI0058'
7284
assert area.part_number.string == '0RMRF7A05'
85+
86+
87+
def test_FruInventory_ignore_checksum_error():
88+
data = b'\x01\x00\x00\x01\x04\x00\x00\xfa\x01\x03\x00vq\xb4\xcaASRockRack\xc0\xc0\xc0\xc0\xc1\x00\x1b\x01\x03\x00\xcaASRockRack\xc0\xc0\xc0\xc0\xc0\xc0\xc1\x00\x00M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
89+
90+
with pytest.raises(DecodingError):
91+
FruInventory(data, ignore_checksum=False)
92+
93+
inv = FruInventory(data, ignore_checksum=True)
94+
95+
assert inv.board_info_area.manufacturer.string == 'ASRockRack'
96+
assert inv.product_info_area.manufacturer.string == 'ASRockRack'

0 commit comments

Comments
 (0)