|
@@ -0,0 +1,107 @@
|
|
|
+using Invercargill;
|
|
|
+using InvercargillJson;
|
|
|
+
|
|
|
+namespace Binman {
|
|
|
+
|
|
|
+ public class Updater {
|
|
|
+
|
|
|
+ public void update(Enumerable<CompositionComponent>? components = null) throws Error{
|
|
|
+ var comps = components ?? get_composition().to_series();
|
|
|
+
|
|
|
+ foreach (var component in comps) {
|
|
|
+ var folder = File.new_build_filename("/var/binman/", component.name);
|
|
|
+ if(!folder.query_exists()) {
|
|
|
+ folder.make_directory_with_parents();
|
|
|
+ }
|
|
|
+
|
|
|
+ var min_serial = 0;
|
|
|
+ var latest_syml = File.new_build_filename(folder.get_path(), "latest");
|
|
|
+ if(latest_syml.query_exists()) {
|
|
|
+ min_serial = new ManifestFile(latest_syml).read_header().serial;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private File download_manifest(CompositionComponent component, int64 min_serial = 0) throws IOError {
|
|
|
+
|
|
|
+ var downloaded = false;
|
|
|
+ foreach (var uri in component.uris) {
|
|
|
+ DataInputStream stream = null;
|
|
|
+ try {
|
|
|
+ var remote_file = File.new_for_uri(uri);
|
|
|
+ var decompressor = new ZlibDecompressor(ZlibCompressorFormat.GZIP);
|
|
|
+ stream = new DataInputStream(new ConverterInputStream(remote_file.read(), decompressor));
|
|
|
+
|
|
|
+ // Read header
|
|
|
+ var header_line = stream.read_line();
|
|
|
+ var header = ManifestHeader.get_mapper().materialise(new JsonElement.from_string(header_line).as<JsonObject>());
|
|
|
+
|
|
|
+ if(header.serial < min_serial){
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(header.key != component.key) {
|
|
|
+ throw new ManifestError.KEY_MISMATCH("Key in manifest header does not match key in component");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Download and verify file
|
|
|
+ FileIOStream tmp_stream;
|
|
|
+ var download_tmp = File.new_tmp(null, out tmp_stream);
|
|
|
+ var checksum = new Checksum(ChecksumType.SHA512);
|
|
|
+ var compressor = new ZlibCompressor(ZlibCompressorFormat.GZIP, 9);
|
|
|
+ var save_stream = new DataOutputStream(new ConverterOutputStream(tmp_stream.output_stream, compressor));
|
|
|
+
|
|
|
+ var line = header_line;
|
|
|
+ ManifestSignature signature = null;
|
|
|
+ while (true) {
|
|
|
+ save_stream.put_string(line);
|
|
|
+
|
|
|
+ var obj = new JsonElement.from_string(line).as<JsonObject>();
|
|
|
+ // Don't include signature object in checksum
|
|
|
+ if(obj.has("signature")) {
|
|
|
+ signature = ManifestSignature.get_mapper().materialise(obj);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ checksum.update(line.data, line.data.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate checksum
|
|
|
+ var checksum_bytes = new uint8[ChecksumType.SHA512.get_length()];
|
|
|
+ size_t size = checksum_bytes.length;
|
|
|
+ checksum.get_digest(checksum_bytes, ref size);
|
|
|
+
|
|
|
+ // Verify signature
|
|
|
+ var signed_checksum = Sodium.Asymmetric.Signing.verify(signature.signature.to_array(), component.key.to_array());
|
|
|
+ if(!new BinaryData.from_byte_array(signed_checksum).equals(new BinaryData.from_byte_array(checksum_bytes))) {
|
|
|
+ throw new ManifestError.INVALID_SIGNATURE("Downloaded manifest has invalid signature");
|
|
|
+ }
|
|
|
+
|
|
|
+ // Install new manifest
|
|
|
+ var dest = File.new_build_filename("/var/binman/", component.name, )
|
|
|
+
|
|
|
+ }
|
|
|
+ catch(Error e) {
|
|
|
+ warning(@"Download \"$(descriptor.remote_path)\" from remote \"$remote\" failed: $(e.message)");
|
|
|
+ }
|
|
|
+ finally {
|
|
|
+ if(stream != null) {
|
|
|
+ stream.close();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if(!downloaded) {
|
|
|
+ throw new IOError.FAILED(@"Could not download manifest for component \"$(component.name)\", all remotes were tried.");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+}
|