You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
update: fast reed-solomon implementations (100x boost) + option --ecc_algo to choose the implementation + more reliable decoding with new ecc check (even if hash is tampered, message block can still be recovered if ecc allows)
Copy file name to clipboardExpand all lines: header_ecc.py
+35-64Lines changed: 35 additions & 64 deletions
Original file line number
Diff line number
Diff line change
@@ -62,7 +62,7 @@
62
62
# the intra-ecc on filepath is the hardest because we won't know the size (not fixed-length), but we can use a field_delim. For intra-ecc on hash this is easy if the hash is fixed-length like MD5: we can precisely compute the length of the ECC, thus it will just be another field to extract in entry_fields.
63
63
#
64
64
65
-
__version__="1.1"
65
+
__version__="1.2"
66
66
67
67
# Include the lib folder in the python import path (so that packaged modules can be easily called, such as gooey which always call its submodules via gooey parent module)
68
68
importsys, os
@@ -82,15 +82,11 @@
82
82
fromlib.teeimportTee# Redirect print output to the terminal as well as in a log file
83
83
#import pprint # Unnecessary, used only for debugging purposes
84
84
85
-
importlib.brownanrs.rsasbrownanrs# Pure python implementation of Reed-Solomon with configurable max_block_size and automatic error detection (you don't have to specify where they are).
86
-
rsmode=1# to allow different implementations (possibly more efficient) of Reed-Solomon in the future
'''Generalized feature scaling (unused, only useful for variable error correction rate in the future)'''
130
126
returna+float(x-xmin) * (b-a) / (xmax-xmin)
131
127
132
-
classHasher(object):
133
-
'''Class to provide a hasher object with various hashing algorithms. What's important is to provide the __len__ so that we can easily compute the block size of ecc entries. Must only use fixed size hashers for the rest of the script to work properly.'''
134
-
def__init__(self, algo="md5"):
135
-
self.algo=algo.lower()
136
-
137
-
defhash(self, mes):
138
-
ifself.algo=="md5":
139
-
returnhashlib.md5(mes).hexdigest()
140
-
141
-
def__len__(self):
142
-
ifself.algo=="md5":
143
-
return32
144
-
145
-
classECC(object):
146
-
'''ECC manager, which provide a modular way to use different kinds of ecc algorithms.'''
147
-
def__init__(self, n, k):
148
-
self.ecc_manager=brownanrs.RSCoder(n, k)
149
-
self.n=n
150
-
self.k=k
151
-
152
-
defencode(self, message):
153
-
message, _=self.pad(message)
154
-
ifrsmode==1:
155
-
mesecc=self.ecc_manager.encode(message)
156
-
ecc=mesecc[len(message):]
157
-
returnecc
158
-
159
-
defdecode(self, message, ecc):
160
-
message, pad=self.pad(message)
161
-
ifrsmode==1:
162
-
res=self.ecc_manager.decode(message+ecc, nostrip=True) # Avoid automatic stripping because we are working with binary streams, thus we should manually strip padding only when we know we padded
163
-
ifpad: # Strip the null bytes if we padded the message before decoding
164
-
res=res[len(pad):len(res)]
165
-
returnres
166
-
167
-
defpad(self, message):
168
-
'''Automatically pad with null bytes a message if too small, or leave unchanged if not necessary. This allows to keep track of padding and strip the null bytes after decoding reliably with binary data.'''
'''Read the next ecc entry (string) in the ecc file. This will read any string length between two entrymarkers. The reading is very tolerant, so it will always return any valid entry (but also scrambled entries if any, but the decoding will ensure everything's ok).'''
help='What algorithm use to generate and verify the ECC? Values possible: 1-4. 1 is the formal, fully verified Reed-Solomon in base 3 ; 2 is a faster implementation but still based on the formal base 3 ; 3 is an even faster implementation but based on another library which may not be correct ; 4 is the fastest implementation supporting US FAA ADSB UAT RS FEC standard but is totally incompatible with the other three (a text encoded with any of 1-3 modes will be decodable with any one of them).', **widget_text)
help='Reed-Solomon max block size (maximum = 255). It is advised to keep it at the maximum for more resilience (see comments at the top of the script for more info).', **widget_text)
help='Path to the error file generated by RFIGC.py (this specify in csv format the list of files to check, and only those files will be checked and repaired). Do not specify this argument if you want to check and repair all files.', **widget_file)
help='On correction, if the file size differs from when the ecc file was generated, ignore and try to correct anyway (this may work with file where data was appended without changing the rest. For compressed formats like zip, this will probably fail).')
help='On correction, block corruption is only checked with the hash (the ecc will still be checked after correction, but not before). If no_fast_check is enabled, then ecc will also be checked before. This allows to find blocks corrupted by malicious intent (the block is corrupted but the hash has been corrupted as well to match the corrupted block, because it\'s almost impossible that following a hardware or logical fault, the hash match the corrupted block).')
ifalways_include_ext: always_include_ext=tuple(['.'+extforextinalways_include_ext.split('|')]) # prepare a tuple of extensions (prepending with a dot) so that str.endswith() works (it doesn't with a list, only a tuple)
# Precomputing is important so that the user can know what size to expect before starting (and how much time it will take...).
@@ -561,6 +521,7 @@ def main(argv=None):
561
521
db.write("**PYHEADERECCv%s**\n"% (''.join([x*3forxin__version__]))) # each character in the version will be repeated 3 times, so that in case of tampering, a majority vote can try to disambiguate
562
522
# Write the parameters (they are NOT reloaded automatically, you have to specify them at commandline! It's the user role to memorize those parameters (using any means: own brain memory, keep a copy on paper, on email, etc.), so that the parameters are NEVER tampered. The parameters MUST be ultra reliable so that errors in the ECC file can be more efficiently recovered.
563
523
foriinxrange(3): db.write("** Parameters: "+" ".join(sys.argv[1:]) +"\n") # copy them 3 times just to be redundant in case of ecc file corruption
524
+
db.write("** Generated under %s\n"%ecc_manager.description())
564
525
# NOTE: there's NO HEADER for the ecc file! Ecc entries are all independent of each others, you just need to supply the decoding arguments at commandline, and the ecc entries can be decoded. This is done on purpose to be remove the risk of critical spots in ecc file (there is still a critical spot in the filepath and on hashes, see intra-ecc in todo).
565
526
566
527
# Processing ecc on files
@@ -672,17 +633,27 @@ def main(argv=None):
672
633
# For each message block, check the message with hash and repair with ecc if necessary
673
634
fori, einenumerate(entry_asm):
674
635
# If the message block has a different hash, it was corrupted (or the hash is corrupted, or both)
exceptReedSolomonError, e: # the reedsolo lib may raise an exception when it can't decode. We ensure that we can still continue to decode the rest of the file, and the other files.
643
+
repaired_block=None
644
+
repaired_ecc=None
645
+
print(e)
680
646
# Check if the repair was successful.
681
-
ifrepaired_blockandhasher.hash(repaired_block) ==e["hash"]: # If the hash now match the repaired message block, we commit the new block
else: # Else the hash does not match: the repair failed (either because the ecc is too much tampered, or because the hash is corrupted. Either way, we don't commit). # TODO: maybe it's just the hash that was corrupted and the repair worked out, we need more resiliency against that by computing ecc for the hash too (I call this: intra-ecc).
685
-
ptee.write("Error: file %s could not repair block %i (hash mismatch). You may try with Dans-labs/bit-recover."% (relfilepath, i)) # you need to code yourself to use bit-recover, it's in perl but it should work given the hash computed by this script and the corresponding message block.
elifnothash_ok: ptee.write("File %s: block %i probably repaired with matching ecc check but with a hash error (assume the hash was corrupted)."% (relfilepath, i))
654
+
elifnotecc_ok: ptee.write("File %s: block %i probably repaired with matching hash but with ecc check error (assume the ecc was partially corrupted)."% (relfilepath, i))
655
+
else: # Else the hash and the ecc check do not match: the repair failed (either because the ecc is too much tampered, or because the hash is corrupted. Either way, we don't commit). # TODO: maybe it's just the hash that was corrupted and the repair worked out, we need more resiliency against that by computing ecc for the hash too (I call this: intra-ecc).
656
+
ptee.write("Error: file %s could not repair block %i (both hash and ecc check mismatch)."% (relfilepath, i)) # you need to code yourself to use bit-recover, it's in perl but it should work given the hash computed by this script and the corresponding message block.
686
657
repaired_partially=True
687
658
# -- Reconstruct/Copying the repaired file
688
659
# If this file had a corruption in one of its header blocks, then we will reconstruct the file header and then append the rest of the file (which can then be further repaired by other tools such as PAR2).
0 commit comments