Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 45 additions & 21 deletions lib/confuse/fwup.ex
Original file line number Diff line number Diff line change
Expand Up @@ -192,15 +192,15 @@ defmodule Confuse.Fwup do
def validate_delta(source_meta_conf, target_meta_conf, using_fwup_version \\ nil) do
with {:ok, source} <- Confuse.parse(source_meta_conf),
{:ok, target} <- Confuse.parse(target_meta_conf) do
s = get_feature_by_resource(source)
t = get_feature_by_resource(target)
source_features_by_resource = get_features_by_resource(source)
target_features_by_resource = get_features_by_resource(target)

fwup_warnings =
if using_fwup_version do
target_features =
t
target_features_by_resource
|> Map.values()
|> Features.squash(t["require-fwup-version"])
|> Features.squash(target_features_by_resource["require-fwup-version"])

if Version.compare(using_fwup_version, target_features.delta_fwup_version) == :gt do
[]
Expand All @@ -213,12 +213,15 @@ defmodule Confuse.Fwup do
[]
end

# Validate any resources in the source that exist in the target
result =
s
|> Enum.flat_map(fn {resource, v} ->
case Enum.find(t, fn {r, _} -> r == resource end) do
{_, target_features} ->
validate_delta_resource(resource, v, target_features)
source_features_by_resource
|> Enum.flat_map(fn {source_resource, source_features} ->
case Enum.find(target_features_by_resource, fn {target_resource, _} ->
target_resource == source_resource
end) do
{_target_resource, target_features} ->
validate_delta_resource(source_resource, source_features, target_features)

# If the resource doesn't exist in the target, it might have just been removed
nil ->
Expand Down Expand Up @@ -262,20 +265,41 @@ defmodule Confuse.Fwup do
end
end

defp validate_delta_resource(resource, sv, tv) do
[
(tv.raw_deltas_valid? and not sv.raw_write?) &&
"#{resource}: Target uses raw deltas but source has no raw writes.",
(tv.fat_deltas_valid? and not sv.fat_write?) &&
"#{resource}: Target uses FAT deltas but source has no FAT writes.",
(tv.raw_deltas_valid? and sv.raw_write_cipher? and sv.raw_write_secret? and
not (tv.delta_source_raw_options_cipher? and tv.delta_source_raw_options_secret?)) &&
"#{resource}: Target uses raw deltas and source firmware uses encryption for the same resource but target firmware has no cipher or secret options for the resource. This should not be able to work."
]
|> Enum.filter(&is_binary/1)
defp validate_delta_resource(resource, source_features, target_features) do
errors = []

errors =
if target_features.raw_deltas_valid? and not source_features.raw_write? do
["#{resource}: Target uses raw deltas but source has no raw writes." | errors]
else
errors
end

errors =
if target_features.fat_deltas_valid? and not source_features.fat_write? do
["#{resource}: Target uses FAT deltas but source has no FAT writes." | errors]
else
errors
end

errors =
if target_features.raw_deltas_valid? and
source_features.raw_write_cipher? and
source_features.raw_write_secret? and
not (target_features.delta_source_raw_options_cipher? and
target_features.delta_source_raw_options_secret?) do
[
"#{resource}: Target uses raw deltas and source firmware uses encryption for the same resource but target firmware has no cipher or secret options for the resource. This should not be able to work."
| errors
]
else
errors
end

errors
end

defp get_feature_by_resource(parsed) do
defp get_features_by_resource(parsed) do
parsed
|> get_tasks()
|> reduce_on_resource(%{}, &check_feature/3)
Expand Down
166 changes: 99 additions & 67 deletions test/fwup_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -117,76 +117,108 @@ defmodule Confuse.FwupTest do
Confuse.Fwup.get_feature_usage("test/fixtures/with_encrypted_deltas.conf")
end

test "validate delta, essentially all configs work with themselves" do
~w(
blank.conf
fat_deltas.conf
high_requirement.conf
rpi4-fwup.conf
rpi4-meta.conf
with_deltas.conf
encrypted.conf
libconfuse-test.conf
raw_deltas.conf
with_encrypted_deltas.conf)
|> Enum.each(fn file ->
conf = File.read!(Path.join("test/fixtures", file))
assert {:ok, ^file} = {Confuse.Fwup.validate_delta(conf, conf), file}
end)
end

test "validate firmware packaged meta.conf, rpi4, no fat_write" do
source_conf = File.read!("test/fixtures/rpi4-meta-no-fat-write.conf")
target_conf = File.read!("test/fixtures/rpi4-meta-fat-delta.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses FAT deltas but source has no FAT writes"
end

test "validate firmware packaged meta.conf, rpi4, no raw_write" do
source_conf = File.read!("test/fixtures/rpi4-meta-no-raw-write.conf")
target_conf = File.read!("test/fixtures/rpi4-meta.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses raw deltas but source has no raw writes"
end

test "validate delta, target uses raw deltas, source firmware is encrypted target firmware missing encrypted deltas" do
source_conf = File.read!("test/fixtures/encrypted.conf")
target_conf = File.read!("test/fixtures/with_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses raw deltas and source firmware uses encryption for the same resource"
end

test "validate delta, target uses raw encrypted deltas, low fwup version" do
source_conf = File.read!("test/fixtures/with_encrypted_deltas.conf")
target_conf = File.read!("test/fixtures/with_encrypted_deltas.conf")

assert {:error, [err]} =
Confuse.Fwup.validate_delta(source_conf, target_conf, Version.parse!("1.0.0"))

assert err =~
"Delta firmware update requires fwup version"
end

test "validate delta, unlikely raw deltas" do
source_conf = File.read!("test/fixtures/no_raw_writes.conf")
target_conf = File.read!("test/fixtures/raw_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)
describe "validate_delta/2" do
test "returns :ok when all configs work with themselves" do
~w(
blank.conf
fat_deltas.conf
high_requirement.conf
rpi4-fwup.conf
rpi4-meta.conf
with_deltas.conf
encrypted.conf
libconfuse-test.conf
raw_deltas.conf
with_encrypted_deltas.conf
)
|> Enum.each(fn file ->
conf = File.read!(Path.join("test/fixtures", file))
assert {:ok, ^file} = {Confuse.Fwup.validate_delta(conf, conf), file}
end)
end

test "returns an error when target uses FAT deltas but source has no fat_write" do
source_conf = File.read!("test/fixtures/rpi4-meta-no-fat-write.conf")
target_conf = File.read!("test/fixtures/rpi4-meta-fat-delta.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses FAT deltas but source has no FAT writes"
end

test "returns an error when target uses raw deltas but source has no raw_write" do
source_conf = File.read!("test/fixtures/rpi4-meta-no-raw-write.conf")
target_conf = File.read!("test/fixtures/rpi4-meta.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses raw deltas but source has no raw writes"
end

test "returns an error when target uses raw deltas, source is encrypted but target missing encrypted deltas" do
source_conf = File.read!("test/fixtures/encrypted.conf")
target_conf = File.read!("test/fixtures/with_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~
"Target uses raw deltas and source firmware uses encryption for the same resource"
end

test "returns an error when target uses raw encrypted deltas with low fwup version" do
source_conf = File.read!("test/fixtures/with_encrypted_deltas.conf")
target_conf = File.read!("test/fixtures/with_encrypted_deltas.conf")

assert {:error, [err]} =
Confuse.Fwup.validate_delta(source_conf, target_conf, Version.parse!("1.0.0"))

assert err =~
"Delta firmware update requires fwup version"
end

test "returns an error when target uses unlikely raw deltas" do
source_conf = File.read!("test/fixtures/no_raw_writes.conf")
target_conf = File.read!("test/fixtures/raw_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~ "Target uses raw deltas but source has no raw writes"
end

test "returns an error when target uses unlikely FAT deltas" do
source_conf = File.read!("test/fixtures/no_fat_writes.conf")
target_conf = File.read!("test/fixtures/fat_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)

assert err =~ "Target uses FAT deltas but source has no FAT writes"
end

test "doesn't error when source contains resource missing in target" do
source_conf = """
require-fwup-version="0.5.0"

task complete {
on-resource config.txt {
raw_write(0)
}
on-resource rootfs.img {
raw_write(0)
}
}
"""

assert err =~ "Target uses raw deltas but source has no raw writes"
end
target_conf = """
require-fwup-version="1.6.0"

test "validate delta, unlikely FAT deltas" do
source_conf = File.read!("test/fixtures/no_fat_writes.conf")
target_conf = File.read!("test/fixtures/fat_deltas.conf")
assert {:error, [err]} = Confuse.Fwup.validate_delta(source_conf, target_conf)
task upgrade {
on-resource config.txt {
delta-source-raw-offset=0
delta-source-raw-count=100
raw_write(0)
}
}
"""

assert err =~ "Target uses FAT deltas but source has no FAT writes"
assert :ok == Confuse.Fwup.validate_delta(source_conf, target_conf)
end
end

test "detect fwup file features, high version requirement, no features" do
Expand Down