Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
#!/usr/bin/env ruby
# This script tests par2 recovery when the par2 files themselves are corrupted.
# Process:
# 1. Generate a file containing all 256 possible bytes.
# (More would be better, but it gets slow fast.)
# 2. Generate par2 data for the file.
# 3. Individually corrupt each par2 file at each offset.
# (Write byte 0 unless the offset already contains byte 0; then, write byte 255.)
# (Writing each possible byte would be better, but it gets slow fast.)
# 4. After each corruption, verify the par2 data, then reverse the corruption.
# 5. Produce a summary of what the par2 verification commands' output, along with the frequencies of each output string.
#
# par2 0.6.14 passes this test.
require "fileutils"
TEMP_DIR = "temp_dir"
RESULTS = Hash.new { 0 }
def in_temp_dir(&block)
if Dir.exists?(TEMP_DIR)
FileUtils.rm_rf(TEMP_DIR)
end
Dir.mkdir(TEMP_DIR)
Dir.chdir(TEMP_DIR, &block)
end
def create_par_archive
# Write a file containing all 256 bytes.
test_data = (0...256).map(&:chr).join("")
File.write("data_file", test_data)
# Create the par archive.
`par2 create -R data.par2 data_file`
# Corrupt the data file (the par archive is now the only source of correct data.)
File.write("data_file", "x", 0)
end
def corrupt_file_at_all_offsets(path)
size = File.stat(path).size
# Corrupt each byte of the file separately.
(0...size).each do |offset|
# Back up existing character.
old_char = File.read(path, 1, offset)
# Generate a new character to corrupt with.
corruption_char = old_char == 0.chr ? 255.chr : 0.chr
# Corrupt file.
File.write(path, corruption_char, offset)
# Verify. Par2 exits with 0 on success, 1 if it can recover, and 2 if it
# couldn't recover. Because we're verifying a corrupted file, we always
# expect exit status 1.
result = `par2 verify data.par2`
unless $?.exitstatus == 1
puts
puts result
puts
raise RuntimeError.new("failed")
end
# Tally the par2 verification output text by the number of times it
# occurred.
RESULTS[result] += 1
# Restore.
File.write(path, old_char, offset)
# Print progress.
puts "#{path} %2i%%" % (100 * offset.to_f / size)
end
end
def summarize
RESULTS.to_a.sort_by { |message, times| times }.each do |message, times|
puts
puts "==== This message occurred #{times} times ".ljust(79, "=")
puts
puts message
end
end
def main
in_temp_dir do
create_par_archive
Dir["*.par2"].each do |path|
corrupt_file_at_all_offsets(path)
end
summarize
end
end
main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
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.