reduce memory usage while parsing multiple keyrings from a stream

This commit is contained in:
Vincent Breitmoser 2014-07-31 18:25:20 +02:00
parent aa32c60a0a
commit ecb2c2c2b1
2 changed files with 72 additions and 31 deletions

View File

@ -1,7 +1,6 @@
package org.sufficientlysecure.keychain.pgp; package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.ArmoredOutputStream; import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.SignatureSubpacketTags; import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.bcpg.sig.KeyFlags; import org.spongycastle.bcpg.sig.KeyFlags;
import org.spongycastle.openpgp.PGPKeyFlags; import org.spongycastle.openpgp.PGPKeyFlags;
@ -30,12 +29,9 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.Vector;
/** Wrapper around PGPKeyRing class, to be constructed from bytes. /** Wrapper around PGPKeyRing class, to be constructed from bytes.
* *
@ -108,41 +104,84 @@ public class UncachedKeyRing {
public static UncachedKeyRing decodeFromData(byte[] data) public static UncachedKeyRing decodeFromData(byte[] data)
throws PgpGeneralException, IOException { throws PgpGeneralException, IOException {
List<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data)); Iterator<UncachedKeyRing> parsed = fromStream(new ByteArrayInputStream(data));
if (parsed.isEmpty()) { if ( ! parsed.hasNext()) {
throw new PgpGeneralException("Object not recognized as PGPKeyRing!"); throw new PgpGeneralException("Object not recognized as PGPKeyRing!");
} }
if (parsed.size() > 1) {
throw new PgpGeneralException( UncachedKeyRing ring = parsed.next();
"Expected single keyring in stream, found " + parsed.size());
if (parsed.hasNext()) {
throw new PgpGeneralException("Expected single keyring in stream, found at least two");
} }
return parsed.get(0); return ring;
} }
public static List<UncachedKeyRing> fromStream(InputStream stream) throws IOException { public static Iterator<UncachedKeyRing> fromStream(final InputStream stream) throws IOException {
List<UncachedKeyRing> result = new Vector<UncachedKeyRing>();
while(stream.available() > 0) { return new Iterator<UncachedKeyRing>() {
PGPObjectFactory objectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
// go through all objects in this block UncachedKeyRing mNext = null;
Object obj; PGPObjectFactory mObjectFactory = null;
while ((obj = objectFactory.nextObject()) != null) {
Log.d(Constants.TAG, "Found class: " + obj.getClass()); private void cacheNext() {
if (!(obj instanceof PGPKeyRing)) { if (mNext != null) {
Log.d(Constants.TAG, return;
"Bad object of type " + obj.getClass().getName() + " in stream, proceed with next object...");
// skip object
continue;
} }
result.add(new UncachedKeyRing((PGPKeyRing) obj));
}
}
return result; try {
if (mObjectFactory == null) {
if (stream.available() == 0) {
// end of stream. that's fine
return;
}
mObjectFactory = new PGPObjectFactory(PGPUtil.getDecoderStream(stream));
}
// go through all objects in this block
Object obj;
while ((obj = mObjectFactory.nextObject()) != null) {
Log.d(Constants.TAG, "Found class: " + obj.getClass());
if (!(obj instanceof PGPKeyRing)) {
Log.i(Constants.TAG,
"Skipping object of bad type " + obj.getClass().getName() + " in stream");
// skip object
continue;
}
mNext = new UncachedKeyRing((PGPKeyRing) obj);
return;
}
} catch (IOException e) {
Log.e(Constants.TAG, "IOException while processing stream", e);
}
}
@Override
public boolean hasNext() {
cacheNext();
return mNext != null;
}
@Override
public UncachedKeyRing next() {
try {
cacheNext();
return mNext;
} finally {
mNext = null;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
} }
public void encodeArmored(OutputStream out, String version) throws IOException { public void encodeArmored(OutputStream out, String version) throws IOException {

View File

@ -32,6 +32,7 @@ import org.sufficientlysecure.keychain.util.PositionAwareInputStream;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
public class ImportKeysListLoader public class ImportKeysListLoader
@ -127,11 +128,12 @@ public class ImportKeysListLoader
BufferedInputStream bufferedInput = new BufferedInputStream(progressIn); BufferedInputStream bufferedInput = new BufferedInputStream(progressIn);
try { try {
// parse all keyrings // parse all keyrings
List<UncachedKeyRing> rings = UncachedKeyRing.fromStream(bufferedInput); Iterator<UncachedKeyRing> it = UncachedKeyRing.fromStream(bufferedInput);
for (UncachedKeyRing key : rings) { while (it.hasNext()) {
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), key); UncachedKeyRing ring = it.next();
ImportKeysListEntry item = new ImportKeysListEntry(getContext(), ring);
mData.add(item); mData.add(item);
mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(key.getEncoded())); mParcelableRings.put(item.hashCode(), new ParcelableKeyRing(ring.getEncoded()));
} }
} catch (IOException e) { } catch (IOException e) {
Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e); Log.e(Constants.TAG, "IOException on parsing key file! Return NoValidKeysException!", e);