jgvariant-tool, add-static-delta: Document better, accept modified Base64.

Change-Id: Icad477b34d074164ffdd162afd91f4abf627b979
diff --git a/README.md b/README.md
index 8254404..dc3f37f 100644
--- a/README.md
+++ b/README.md
@@ -87,13 +87,19 @@
 
 #### Adding a static delta to an [OSTree][] summary file
 
-Static delta <code>3...</code> (in hex), between commits <code>1...</code> and <code>2...</code>:
+Superblock checksum `0...`, between commits `3...` and `6...`:
 
-    $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 3333333333333333333333333333333333333333333333333333333333333333 2222222222222222222222222222222222222222222222222222222222222222 1111111111111111111111111111111111111111111111111111111111111111
+    $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 03738040e28e7662e9c9d2599c530ea974e642c9f87e6c00cbaa39a0cdac8d44 66ff167ff35ce87daac817447a9490a262ee75f095f017716a6eb1a9d9eb3350 3d3b3329dca38871f29aeda1bf5854d76c707fa269759a899d0985c91815fe6f
 
-Static delta <code>3...</code> (in hex), between the empty commit and <code>2...</code>:
+Superblock checksum `f...`, between the empty commit and `3...`:
 
-    $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 4444444444444444444444444444444444444444444444444444444444444444 2222222222222222222222222222222222222222222222222222222222222222
+    $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary f481144629474bd88c106e45ac405ebd75b324b0655af1aec14b31786ae1fd61 31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52
+
+Checksums can be given in either hex (64 digits) or a variant of Base64 (43
+digits) where `/` is replaced by `_`.  The latter format is used to identify
+the start and end commits of deltas as part of folder names below `deltas/` in
+the OSTree repository itself.
+
 
 ### Building the tool
 
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
index 744d902..c352e4f 100644
--- a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/Main.java
@@ -61,17 +61,17 @@
  *
  * <h3>Adding a static delta to an OSTree summary file</h3>
  *
- * <p>Static delta <code>3...</code> (in hex), between commits <code>1...</code> and <code>2...
+ * <p>Superblock checksum <code>0...</code>, between commits <code>3...</code> and <code>6...
  * </code>:
  *
  * {@snippet lang="sh" :
- * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 3333333333333333333333333333333333333333333333333333333333333333 2222222222222222222222222222222222222222222222222222222222222222 1111111111111111111111111111111111111111111111111111111111111111
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 03738040e28e7662e9c9d2599c530ea974e642c9f87e6c00cbaa39a0cdac8d44 66ff167ff35ce87daac817447a9490a262ee75f095f017716a6eb1a9d9eb3350 3d3b3329dca38871f29aeda1bf5854d76c707fa269759a899d0985c91815fe6f
  * }
  *
- * <p>Static delta <code>3...</code> (in hex), between the empty commit and <code>2...</code>:
+ * <p>Superblock checksum <code>f...</code>, between the empty commit and <code>3...</code>:
  *
  * {@snippet lang="sh" :
- * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 4444444444444444444444444444444444444444444444444444444444444444 2222222222222222222222222222222222222222222222222222222222222222
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary f481144629474bd88c106e45ac405ebd75b324b0655af1aec14b31786ae1fd61 31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52
  * }
  */
 public final class Main {
diff --git a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
index 2bea23c..db724dd 100644
--- a/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
+++ b/jgvariant-tool/src/main/java/eu/mulk/jgvariant/tool/MainCommand.java
@@ -29,6 +29,7 @@
 import java.util.*;
 import java.util.logging.Logger;
 import java.util.stream.IntStream;
+import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.VisibleForTesting;
 import picocli.AutoComplete;
 import picocli.CommandLine;
@@ -37,7 +38,7 @@
 @Command(
     name = "jgvariant",
     mixinStandardHelpOptions = true,
-    description = "Manipulate files in GVariant format.",
+    header = "Manipulate files in GVariant format.",
     subcommands = {MainCommand.OstreeCommand.class, AutoComplete.GenerateCompletion.class})
 final class MainCommand {
 
@@ -74,35 +75,60 @@
   @Command(
       name = "ostree",
       mixinStandardHelpOptions = true,
-      description = "Manipulate OSTree files.",
+      header = "Manipulate OSTree files.",
       subcommands = {OstreeCommand.SummaryCommand.class})
   static final class OstreeCommand {
 
     @Command(
         name = "summary",
         mixinStandardHelpOptions = true,
-        description = "Manipulate OSTree summary files.")
+        header = "Manipulate OSTree summary files.")
     static final class SummaryCommand extends BaseDecoderCommand<Summary> {
 
-      @Command(mixinStandardHelpOptions = true)
+      @Command(
+          name = "read",
+          mixinStandardHelpOptions = true,
+          header = "Dump an OSTree summary file as human-readable JSON.")
       void read(@Parameters(paramLabel = "<file>", description = "Summary file to read") File file)
           throws IOException {
         read(file, Summary.decoder());
       }
 
-      @Command(name = "add-static-delta", mixinStandardHelpOptions = true)
+      @Command(
+          name = "add-static-delta",
+          mixinStandardHelpOptions = true,
+          header = "Add a static delta to an OSTree summary file.",
+          description =
+              """
+              Checksums can be given in either hex (64 digits) or a variant of Base64 (43
+              digits) where '/' is replaced by '_'.
+
+              In the OSTree repository, static deltas are named based on the <from> and <to>
+              checksums in modified Base64 when stored in the deltas/ directory.  The naming
+              scheme is either <from[0..1]>/<from[2..42]>-<to> if <from> is an actual commit
+              or <to[0..1]>/<to[2..42]> if <from> is the empty commit.
+
+              <superblock-csum> is the SHA256 checksum of the file called 'superblock' that
+              is part of the static delta and contains its metadata.
+              """)
       void addStaticDelta(
           @Parameters(paramLabel = "<file>", description = "Summary file to manipulate.")
               File summaryFile,
-          @Parameters(paramLabel = "<delta>", description = "Checksum of the static delta (hex).")
-              String delta,
-          @Parameters(paramLabel = "<to>", description = "Commit checksum the delta ends at (hex).")
-              String toCommit,
+          @Parameters(
+                  paramLabel = "<superblock-csum>",
+                  description = "Checksum of the static delta superblock (hex/mbase64).")
+              String deltaName,
+          @Parameters(
+                  paramLabel = "<to>",
+                  description = "Commit checksum the delta ends at (hex/mbase64).")
+              String toCommitName,
           @Parameters(
                   paramLabel = "<from>",
                   arity = "0..1",
-                  description = "Commit checksum the delta starts from (hex).")
-              String fromCommit)
+                  description =
+                      "Commit checksum the delta starts from (hex/mbase64).  Defaults to the empty commit.")
+              @Nullable
+              String fromCommitName)
           throws IOException, ParseException {
         var summaryDecoder = Summary.decoder();
 
@@ -111,6 +137,10 @@
         var staticDeltaMapSignature = Signature.parse("a{sv}");
         var checksumSignature = Signature.parse("ay");
 
+        var delta = parseChecksum(deltaName);
+        var toCommit = parseChecksum(toCommitName);
+        var fromCommit = fromCommitName != null ? parseChecksum(fromCommitName) : null;
+
         var metadata = summary.metadata();
         var metadataFields = new LinkedHashMap<>(metadata.fields());
         metadataFields.compute(
@@ -121,8 +151,8 @@
                       ? new LinkedHashMap<>((Map<String, Variant>) v.value())
                       : new LinkedHashMap<>();
               staticDeltas.put(
-                  fromCommit != null ? fromCommit + "-" + toCommit : toCommit,
-                  new Variant(checksumSignature, toByteList(ByteString.ofHex(delta).bytes())));
+                  fromCommit != null ? fromCommit.hex() + "-" + toCommit.hex() : toCommit.hex(),
+                  new Variant(checksumSignature, toByteList(delta.bytes())));
               return new Variant(staticDeltaMapSignature, staticDeltas);
             });
         metadata = new Metadata(metadataFields);
@@ -131,10 +161,6 @@
         encodeFile(summaryFile, summaryDecoder, summary);
       }
 
-      private List<Byte> toByteList(byte[] bytes) {
-        return IntStream.range(0, bytes.length).mapToObj(i -> bytes[i]).toList();
-      }
-
       SummaryCommand() {}
     }
 
@@ -185,6 +211,27 @@
         out.write(thingBytes);
       }
     }
+
+    protected static ByteString parseChecksum(String name) {
+      var bytes =
+          switch (name.length()) {
+            case 64 -> ByteString.ofHex(name);
+            case 43 -> ByteString.ofModifiedBase64(name);
+            default ->
+                throw new IllegalArgumentException(
+                    "Checksums must be either 64 hex digits or 43 mbase64 digits.");
+          };
+
+      if (bytes.size() != 32) {
+        throw new IllegalArgumentException("Checksums must be 32 bytes long.");
+      }
+
+      return bytes;
+    }
+
+    protected static List<Byte> toByteList(byte[] bytes) {
+      return IntStream.range(0, bytes.length).mapToObj(i -> bytes[i]).toList();
+    }
   }
 
   MainCommand() {}
diff --git a/jgvariant-tool/src/main/java/module-info.java b/jgvariant-tool/src/main/java/module-info.java
index 6faa226..3f38283 100644
--- a/jgvariant-tool/src/main/java/module-info.java
+++ b/jgvariant-tool/src/main/java/module-info.java
@@ -55,17 +55,17 @@
  *
  * <h3>Adding a static delta to an OSTree summary file</h3>
  *
- * <p>Static delta <code>3...</code> (in hex), between commits <code>1...</code> and <code>2...
+ * <p>Superblock checksum <code>0...</code>, between commits <code>3...</code> and <code>6...
  * </code>:
  *
  * {@snippet lang="sh" :
- * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 3333333333333333333333333333333333333333333333333333333333333333 2222222222222222222222222222222222222222222222222222222222222222 1111111111111111111111111111111111111111111111111111111111111111
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 03738040e28e7662e9c9d2599c530ea974e642c9f87e6c00cbaa39a0cdac8d44 66ff167ff35ce87daac817447a9490a262ee75f095f017716a6eb1a9d9eb3350 3d3b3329dca38871f29aeda1bf5854d76c707fa269759a899d0985c91815fe6f
  * }
  *
- * <p>Static delta <code>3...</code> (in hex), between the empty commit and <code>2...</code>:
+ * <p>Superblock checksum <code>f...</code>, between the empty commit and <code>3...</code>:
  *
  * {@snippet lang="sh" :
- * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary 4444444444444444444444444444444444444444444444444444444444444444 2222222222222222222222222222222222222222222222222222222222222222
+ * $ jgvariant ostree summary add-static-delta ./jgvariant-ostree/src/test/resources/ostree/summary f481144629474bd88c106e45ac405ebd75b324b0655af1aec14b31786ae1fd61 31c8835d5c9d2c6687a50091c85142d1b2d853ff416a9fb81b4ee30754510d52
  * }
  */
 module eu.mulk.jgvariant.tool {