From e3b37a64d061eabbc4665fc7447e479bd9332721 Mon Sep 17 00:00:00 2001
From: zzz <zzz@mail.i2p>
Date: Sun, 14 Jan 2024 11:44:17 +0000
Subject: [PATCH] Data: Store compressed KeysAndCert

---
 core/java/src/net/i2p/data/Destination.java | 16 ++--
 core/java/src/net/i2p/data/KeysAndCert.java | 87 ++++++++++++++++++---
 2 files changed, 87 insertions(+), 16 deletions(-)

diff --git a/core/java/src/net/i2p/data/Destination.java b/core/java/src/net/i2p/data/Destination.java
index 0df0781e98..6ec59babd9 100644
--- a/core/java/src/net/i2p/data/Destination.java
+++ b/core/java/src/net/i2p/data/Destination.java
@@ -110,7 +110,7 @@ public class Destination extends KeysAndCert {
         _publicKey = pk;
         _signingKey = sk;
         _certificate = c;
-        _padding = padding;
+        setPadding(padding);
     }
 
     /**
@@ -122,10 +122,7 @@ public class Destination extends KeysAndCert {
         int cur = offset;
         System.arraycopy(_publicKey.getData(), 0, target, cur, PublicKey.KEYSIZE_BYTES);
         cur += PublicKey.KEYSIZE_BYTES;
-        if (_padding != null) {
-            System.arraycopy(_padding, 0, target, cur, _padding.length);
-            cur += _padding.length;
-        }
+        cur = writePaddingBytes(target, cur);
         int spkTrunc = Math.min(SigningPublicKey.KEYSIZE_BYTES, _signingKey.length());
         System.arraycopy(_signingKey.getData(), 0, target, cur, spkTrunc);
         cur += spkTrunc;
@@ -166,8 +163,13 @@ public class Destination extends KeysAndCert {
         if (_certificate.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) {
             // cert data included in keys
             rv += 7;
-            if (_padding != null)
-                rv += _padding.length;
+            if (_paddingBlocks > 1) {
+                rv += 32 * _paddingBlocks;
+            } else {
+                byte[] padding = getPadding();
+                if (padding != null)
+                    rv += padding.length;
+            }
         } else {
             rv += _certificate.size();
         }
diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java
index b33cf41a07..70c6760d2a 100644
--- a/core/java/src/net/i2p/data/KeysAndCert.java
+++ b/core/java/src/net/i2p/data/KeysAndCert.java
@@ -38,7 +38,15 @@ public class KeysAndCert extends DataStructureImpl {
     protected SigningPublicKey _signingKey;
     protected Certificate _certificate;
     private Hash __calculatedHash;
-    protected byte[] _padding;
+    // if compressed, 32 bytes only
+    private byte[] _padding;
+    /**
+     *  If compressed, the padding size / 32, else 0
+     *  @since 0.9.62
+     */
+    protected int _paddingBlocks;
+
+    private static final int PAD_COMP_LEN = 32;
 
     public Certificate getCertificate() {
         return _certificate;
@@ -116,10 +124,17 @@ public class KeysAndCert extends DataStructureImpl {
     }
     
     /**
+     * @return the full padding, expanded if stored compressed
      * @since 0.9.16
      */
     public byte[] getPadding() {
-        return _padding;
+        if (_paddingBlocks <= 1)
+            return _padding;
+        byte[] rv = new byte[PAD_COMP_LEN * _paddingBlocks];
+        for (int i = 0; i <_paddingBlocks; i++) {
+            System.arraycopy(_padding, 0, _paddingBlocks, i * PAD_COMP_LEN, PAD_COMP_LEN);
+        }
+        return rv;
     }
     
     /**
@@ -130,6 +145,7 @@ public class KeysAndCert extends DataStructureImpl {
         if (_padding != null)
             throw new IllegalStateException();
         _padding = padding;
+        compressPadding();
     }
     
     /**
@@ -156,7 +172,7 @@ public class KeysAndCert extends DataStructureImpl {
             _certificate = cert;
         }
     }
-    
+
     /**
      * @return null if both are null
      * @since 0.9.42
@@ -172,15 +188,64 @@ public class KeysAndCert extends DataStructureImpl {
         return rv;
     }
 
+    /**
+     * This only does the padding, does not compress the unused 256 byte LS public key.
+     * Savings is 288 bytes for RI and 64 bytes for LS.
+     * @since 0.9.62
+     */
+    private void compressPadding() {
+        _paddingBlocks = 0;
+        // > 32 and a mult. of 32
+        if (_padding == null || (_padding.length & (2 * PAD_COMP_LEN) - 1) != PAD_COMP_LEN)
+            return;
+        int blks = _padding.length / PAD_COMP_LEN;
+        for (int i = 1; i < blks; i++) {
+            if (!DataHelper.eq(_padding, 0, _padding, i, PAD_COMP_LEN)) {
+                return;
+            }
+        }
+        byte[] comp = new byte[PAD_COMP_LEN];
+        System.arraycopy(_padding, 0, comp, 0, PAD_COMP_LEN);
+        _padding = comp;
+        _paddingBlocks = blks;
+    }
+
+    /**
+     * For Destination.writeBytes()
+     * @return the new offset
+     * @since 0.9.62
+     */
+    protected int writePaddingBytes(byte[] target, int off) {
+        if (_padding == null)
+            return off;
+        if (_paddingBlocks > 1) {
+            for (int i = 0; i < _paddingBlocks; i++) {
+                System.arraycopy(_padding, 0, target, off, _padding.length);
+                off += PAD_COMP_LEN;
+            }
+        } else {
+            System.arraycopy(_padding, 0, target, off, _padding.length);
+            off += _padding.length;
+        }
+        return off;
+    }
+
     public void writeBytes(OutputStream out) throws DataFormatException, IOException {
         if ((_certificate == null) || (_publicKey == null) || (_signingKey == null))
             throw new DataFormatException("Not enough data to format the router identity");
         _publicKey.writeBytes(out);
-        if (_padding != null)
-            out.write(_padding);
-        else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES ||
-                 _publicKey.length() < PublicKey.KEYSIZE_BYTES)
+        if (_padding != null) {
+            if (_paddingBlocks <= 1) {
+                out.write(_padding);
+            } else {
+                for (int i = 0; i <_paddingBlocks; i++) {
+                    out.write(_padding, 0, PAD_COMP_LEN);
+                }
+            }
+        } else if (_signingKey.length() < SigningPublicKey.KEYSIZE_BYTES ||
+                 _publicKey.length() < PublicKey.KEYSIZE_BYTES) {
             throw new DataFormatException("No padding set");
+        }
         _signingKey.writeTruncatedBytes(out);
         _certificate.writeBytes(out);
     }
@@ -224,8 +289,12 @@ public class KeysAndCert extends DataStructureImpl {
             buf.append("\n\tPublicKey: ").append(_publicKey);
         }
         buf.append("\n\tSigningPublicKey: ").append(_signingKey);
-        if (_padding != null)
-            buf.append("\n\tPadding: ").append(_padding.length).append(" bytes");
+        if (_padding != null) {
+            int len = _padding.length;
+            if (_paddingBlocks > 1)
+                len *= _paddingBlocks;
+            buf.append("\n\tPadding: ").append(len).append(" bytes");
+        }
         buf.append(']');
         return buf.toString();
     }
-- 
GitLab