From: David ‘Bombe’ Roden Date: Sat, 13 Sep 2014 17:00:57 +0000 (+0200) Subject: Further reduce dependencies on a Sone for downloading. X-Git-Tag: 0.9-rc1^2~3^2~130 X-Git-Url: https://git.pterodactylus.net/?a=commitdiff_plain;h=fdffe49b10613e1d9caacdf62ab99bca06edf3e1;p=Sone.git Further reduce dependencies on a Sone for downloading. --- diff --git a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java index b389587..9d1a7ed 100644 --- a/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java +++ b/src/main/java/net/pterodactylus/sone/core/FreenetInterface.java @@ -17,11 +17,15 @@ package net.pterodactylus.sone.core; +import static freenet.keys.USK.create; +import static java.lang.String.format; +import static java.util.logging.Level.WARNING; +import static net.pterodactylus.sone.freenet.Key.routingKey; + import java.net.MalformedURLException; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -182,33 +186,30 @@ public class FreenetInterface { } } - public void registerUsk(final Sone sone, final SoneUpdater soneUpdater) { + public void registerActiveUsk(FreenetURI requestUri, + USKCallback uskCallback) { try { - logger.log(Level.FINE, String.format("Registering Sone “%s” for USK updates at %s…", sone, sone.getRequestUri().setMetaString(new String[] { "sone.xml" }))); - USKCallback uskCallback = new USKCallback() { - - @Override - @SuppressWarnings("synthetic-access") - public void onFoundEdition(long edition, USK key, ObjectContainer objectContainer, ClientContext clientContext, boolean metadata, short codec, byte[] data, boolean newKnownGood, boolean newSlotToo) { - logger.log(Level.FINE, String.format("Found USK update for Sone “%s” at %s, new known good: %s, new slot too: %s.", sone, key, newKnownGood, newSlotToo)); - soneUpdater.updateSone(edition); - } - - @Override - public short getPollingPriorityProgress() { - return RequestStarter.INTERACTIVE_PRIORITY_CLASS; - } + soneUskCallbacks.put(routingKey(requestUri), uskCallback); + node.clientCore.uskManager.subscribe(create(requestUri), + uskCallback, true, (RequestClient) client); + } catch (MalformedURLException mue1) { + logger.log(WARNING, format("Could not subscribe USK “%s”!", + requestUri), mue1); + } + } - @Override - public short getPollingPriorityNormal() { - return RequestStarter.INTERACTIVE_PRIORITY_CLASS; - } - }; - soneUskCallbacks.put(sone.getId(), uskCallback); - boolean runBackgroundFetch = (System.currentTimeMillis() - sone.getTime()) < TimeUnit.DAYS.toMillis(7); - node.clientCore.uskManager.subscribe(USK.create(sone.getRequestUri()), uskCallback, runBackgroundFetch, (RequestClient) client); + public void registerPassiveUsk(FreenetURI requestUri, + USKCallback uskCallback) { + try { + soneUskCallbacks.put(routingKey(requestUri), uskCallback); + node.clientCore + .uskManager + .subscribe(create(requestUri), uskCallback, false, + (RequestClient) client); } catch (MalformedURLException mue1) { - logger.log(Level.WARNING, String.format("Could not subscribe USK “%s”!", sone.getRequestUri()), mue1); + logger.log(WARNING, + format("Could not subscribe USK “%s”!", requestUri), + mue1); } } diff --git a/src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java b/src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java index d59f1f5..1e1484d 100644 --- a/src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java +++ b/src/main/java/net/pterodactylus/sone/core/SoneDownloaderImpl.java @@ -17,12 +17,17 @@ package net.pterodactylus.sone.core; +import static java.lang.String.format; +import static java.lang.System.currentTimeMillis; +import static java.util.concurrent.TimeUnit.DAYS; + import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -46,9 +51,15 @@ import net.pterodactylus.util.xml.SimpleXML; import net.pterodactylus.util.xml.XML; import freenet.client.FetchResult; +import freenet.client.async.ClientContext; +import freenet.client.async.USKCallback; import freenet.keys.FreenetURI; +import freenet.keys.USK; +import freenet.node.RequestStarter; import freenet.support.api.Bucket; +import com.db4o.ObjectContainer; + import com.google.common.annotations.VisibleForTesting; import org.w3c.dom.Document; @@ -103,16 +114,46 @@ public class SoneDownloaderImpl extends AbstractService implements SoneDownloade if (!sones.add(sone)) { freenetInterface.unregisterUsk(sone); } - freenetInterface.registerUsk(sone, new SoneUpdater() { + final USKCallback uskCallback = new USKCallback() { + @Override - public void updateSone(long edition) { + @SuppressWarnings("synthetic-access") + public void onFoundEdition(long edition, USK key, + ObjectContainer objectContainer, + ClientContext clientContext, boolean metadata, + short codec, byte[] data, boolean newKnownGood, + boolean newSlotToo) { + logger.log(Level.FINE, format( + "Found USK update for Sone “%s” at %s, new known good: %s, new slot too: %s.", + sone, key, newKnownGood, newSlotToo)); if (edition > sone.getLatestEdition()) { sone.setLatestEdition(edition); new Thread(fetchSoneAction(sone), "Sone Downloader").start(); } } - }); + + @Override + public short getPollingPriorityProgress() { + return RequestStarter.INTERACTIVE_PRIORITY_CLASS; + } + + @Override + public short getPollingPriorityNormal() { + return RequestStarter.INTERACTIVE_PRIORITY_CLASS; + } + }; + if (soneHasBeenActiveRecently(sone)) { + freenetInterface.registerActiveUsk(sone.getRequestUri(), + uskCallback); + } else { + freenetInterface.registerPassiveUsk(sone.getRequestUri(), + uskCallback); + } + } + + private boolean soneHasBeenActiveRecently(Sone sone) { + return (currentTimeMillis() - sone.getTime()) < DAYS.toMillis(7); } private void fetchSone(Sone sone) { diff --git a/src/main/java/net/pterodactylus/sone/core/SoneUpdater.java b/src/main/java/net/pterodactylus/sone/core/SoneUpdater.java deleted file mode 100644 index 5cdefbe..0000000 --- a/src/main/java/net/pterodactylus/sone/core/SoneUpdater.java +++ /dev/null @@ -1,15 +0,0 @@ -package net.pterodactylus.sone.core; - -import net.pterodactylus.sone.data.Sone; - -/** - * Component that decides whether a Sone needs to be downloaded because a - * newer edition was found. - * - * @author David ‘Bombe’ Roden - */ -public interface SoneUpdater { - - void updateSone(long edition); - -} diff --git a/src/main/java/net/pterodactylus/sone/data/Sone.java b/src/main/java/net/pterodactylus/sone/data/Sone.java index 2263192..1b76588 100644 --- a/src/main/java/net/pterodactylus/sone/data/Sone.java +++ b/src/main/java/net/pterodactylus/sone/data/Sone.java @@ -27,13 +27,17 @@ import java.util.Comparator; import java.util.List; import java.util.Set; -import net.pterodactylus.sone.core.Options; +import javax.annotation.Nonnegative; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + import net.pterodactylus.sone.freenet.wot.Identity; import net.pterodactylus.sone.freenet.wot.OwnIdentity; import net.pterodactylus.sone.template.SoneAccessor; import freenet.keys.FreenetURI; +import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.primitives.Ints; @@ -167,6 +171,17 @@ public interface Sone extends Identified, Fingerprintable, Comparable { } }; + public static final Function toSoneXmlUri = + new Function() { + @Nonnull + @Override + public String apply(@Nullable Sone input) { + return input.getRequestUri() + .setMetaString(new String[] { "sone.xml" }) + .toString(); + } + }; + /** * Returns the identity of this Sone. * diff --git a/src/main/java/net/pterodactylus/sone/freenet/Key.java b/src/main/java/net/pterodactylus/sone/freenet/Key.java new file mode 100644 index 0000000..f21e2f6 --- /dev/null +++ b/src/main/java/net/pterodactylus/sone/freenet/Key.java @@ -0,0 +1,67 @@ +package net.pterodactylus.sone.freenet; + +import static freenet.support.Base64.encode; +import static java.lang.String.format; + +import freenet.keys.FreenetURI; + +import com.google.common.annotations.VisibleForTesting; + +/** + * Encapsulates the parts of a {@link FreenetURI} that do not change while + * being converted from SSK to USK and/or back. + * + * @author David ‘Bombe’ Roden + */ +public class Key { + + private final byte[] routingKey; + private final byte[] cryptoKey; + private final byte[] extra; + + private Key(byte[] routingKey, byte[] cryptoKey, byte[] extra) { + this.routingKey = routingKey; + this.cryptoKey = cryptoKey; + this.extra = extra; + } + + @VisibleForTesting + public String getRoutingKey() { + return encode(routingKey); + } + + @VisibleForTesting + public String getCryptoKey() { + return encode(cryptoKey); + } + + @VisibleForTesting + public String getExtra() { + return encode(extra); + } + + public FreenetURI toUsk(String docName, long edition, String... paths) { + return new FreenetURI("USK", docName, paths, routingKey, cryptoKey, + extra, edition); + } + + public FreenetURI toSsk(String docName, String... paths) { + return new FreenetURI("SSK", docName, paths, routingKey, cryptoKey, + extra); + } + + public FreenetURI toSsk(String docName, long edition, String... paths) { + return new FreenetURI("SSK", format("%s-%d", docName, edition), paths, + routingKey, cryptoKey, extra, edition); + } + + public static Key from(FreenetURI freenetURI) { + return new Key(freenetURI.getRoutingKey(), freenetURI.getCryptoKey(), + freenetURI.getExtra()); + } + + public static String routingKey(FreenetURI freenetURI) { + return from(freenetURI).getRoutingKey(); + } + +} diff --git a/src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java b/src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java index d2a1121..aa261f5 100644 --- a/src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java +++ b/src/test/java/net/pterodactylus/sone/core/FreenetInterfaceTest.java @@ -5,9 +5,6 @@ import static freenet.client.InsertException.INTERNAL_ERROR; import static freenet.keys.InsertableClientSSK.createRandom; import static freenet.node.RequestStarter.INTERACTIVE_PRIORITY_CLASS; import static freenet.node.RequestStarter.PREFETCH_PRIORITY_CLASS; -import static java.lang.System.currentTimeMillis; -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.SECONDS; import static net.pterodactylus.sone.Matchers.delivers; import static net.pterodactylus.sone.TestUtil.setFinalField; import static org.hamcrest.MatcherAssert.assertThat; @@ -19,7 +16,6 @@ import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyShort; import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -31,7 +27,6 @@ import static org.mockito.Mockito.withSettings; import java.io.IOException; import java.net.MalformedURLException; import java.util.HashMap; -import java.util.concurrent.CountDownLatch; import net.pterodactylus.sone.TestUtil; import net.pterodactylus.sone.core.FreenetInterface.Callback; @@ -73,8 +68,6 @@ import com.google.common.eventbus.EventBus; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; /** * Unit test for {@link FreenetInterface}. @@ -92,8 +85,6 @@ public class FreenetInterfaceTest { private FreenetInterface freenetInterface; private final Sone sone = mock(Sone.class); private final ArgumentCaptor callbackCaptor = forClass(USKCallback.class); - private final SoneDownloader soneDownloader = mock(SoneDownloader.class); - private final SoneUpdater soneUpdater = mock(SoneUpdater.class); private final Image image = mock(Image.class); private InsertToken insertToken; @@ -211,25 +202,11 @@ public class FreenetInterfaceTest { @Test public void soneWithWrongRequestUriWillNotBeSubscribed() throws MalformedURLException { when(sone.getRequestUri()).thenReturn(new FreenetURI("KSK@GPLv3.txt")); - freenetInterface.registerUsk(sone, null); + freenetInterface.registerUsk(new FreenetURI("KSK@GPLv3.txt"), null); verify(uskManager, never()).subscribe(any(USK.class), any(USKCallback.class), anyBoolean(), any(RequestClient.class)); } @Test - public void registeringAUskForARecentlyModifiedSone() throws MalformedURLException { - when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1)); - freenetInterface.registerUsk(sone, null); - verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), eq(true), eq((RequestClient) highLevelSimpleClient)); - } - - @Test - public void registeringAUskForAnOldSone() throws MalformedURLException { - when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(365)); - freenetInterface.registerUsk(sone, null); - verify(uskManager).subscribe(any(USK.class), any(USKCallback.class), eq(false), eq((RequestClient) highLevelSimpleClient)); - } - - @Test public void registeringAUsk() { FreenetURI freenetUri = createRandom(randomSource, "test-0").getURI().uskForSSK(); Callback callback = mock(Callback.class); @@ -268,39 +245,26 @@ public class FreenetInterfaceTest { } @Test - public void unregisteringARegisteredSoneUnregistersTheSone() { - freenetInterface.registerUsk(sone, null); + public void unregisteringARegisteredSoneUnregistersTheSone() + throws MalformedURLException { + freenetInterface.registerActiveUsk(sone.getRequestUri(), mock(USKCallback.class)); freenetInterface.unregisterUsk(sone); verify(uskManager).unsubscribe(any(USK.class), any(USKCallback.class)); } @Test public void unregisteringASoneWithAWrongRequestKeyWillNotUnsubscribe() throws MalformedURLException { - freenetInterface.registerUsk(sone, null); when(sone.getRequestUri()).thenReturn(new FreenetURI("KSK@GPLv3.txt")); + freenetInterface.registerUsk(sone.getRequestUri(), null); freenetInterface.unregisterUsk(sone); verify(uskManager, never()).unsubscribe(any(USK.class), any(USKCallback.class)); } @Test - public void callbackPrioritiesAreInteractive() { - freenetInterface.registerUsk(sone, null); - assertThat(callbackCaptor.getValue().getPollingPriorityNormal(), is(INTERACTIVE_PRIORITY_CLASS)); - assertThat(callbackCaptor.getValue().getPollingPriorityProgress(), is(INTERACTIVE_PRIORITY_CLASS)); - } - - @Test - public void callbackForRegisteredSoneWithHigherEditionTriggersDownload() throws InterruptedException { - freenetInterface.registerUsk(sone, soneUpdater); - callbackCaptor.getValue().onFoundEdition(1, null, null, null, false, (short) 0, null, false, false); - verify(soneUpdater).updateSone(1); - } - - @Test public void callbackForNormalUskUsesDifferentPriorities() { Callback callback = mock(Callback.class); - FreenetURI uri = createRandom(randomSource, "test-0").getURI().uskForSSK(); - freenetInterface.registerUsk(uri, callback); + FreenetURI soneUri = createRandom(randomSource, "test-0").getURI().uskForSSK(); + freenetInterface.registerUsk(soneUri, callback); assertThat(callbackCaptor.getValue().getPollingPriorityNormal(), is(PREFETCH_PRIORITY_CLASS)); assertThat(callbackCaptor.getValue().getPollingPriorityProgress(), is(INTERACTIVE_PRIORITY_CLASS)); } diff --git a/src/test/java/net/pterodactylus/sone/core/SoneDownloaderTest.java b/src/test/java/net/pterodactylus/sone/core/SoneDownloaderTest.java index 24a1ac9..0f1c2a2 100644 --- a/src/test/java/net/pterodactylus/sone/core/SoneDownloaderTest.java +++ b/src/test/java/net/pterodactylus/sone/core/SoneDownloaderTest.java @@ -1,7 +1,9 @@ package net.pterodactylus.sone.core; import static com.google.common.base.Optional.of; +import static java.lang.System.currentTimeMillis; import static java.util.UUID.randomUUID; +import static java.util.concurrent.TimeUnit.DAYS; import static net.pterodactylus.sone.data.Sone.SoneStatus.downloading; import static net.pterodactylus.sone.data.Sone.SoneStatus.idle; import static net.pterodactylus.sone.data.Sone.SoneStatus.unknown; @@ -33,6 +35,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.TimeUnit; import net.pterodactylus.sone.core.FreenetInterface.Fetched; import net.pterodactylus.sone.data.Album; @@ -52,9 +55,15 @@ import net.pterodactylus.sone.freenet.wot.Identity; import freenet.client.ClientMetadata; import freenet.client.FetchResult; +import freenet.client.async.ClientContext; +import freenet.client.async.USKCallback; import freenet.keys.FreenetURI; +import freenet.keys.USK; +import freenet.node.RequestStarter; import freenet.support.api.Bucket; +import com.db4o.ObjectContainer; + import com.google.common.base.Optional; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableSet; @@ -75,7 +84,6 @@ public class SoneDownloaderTest { private final Core core = mock(Core.class); private final FreenetInterface freenetInterface = mock(FreenetInterface.class); private final SoneDownloaderImpl soneDownloader = new SoneDownloaderImpl(core, freenetInterface); - private final SoneUpdater soneUpdater = mock(SoneUpdater.class); private final FreenetURI requestUri = mock(FreenetURI.class); private final Sone sone = mock(Sone.class); private final PostBuilder postBuilder = mock(PostBuilder.class); @@ -97,6 +105,11 @@ public class SoneDownloaderTest { when(sone.getId()).thenReturn("identity"); when(sone.getIdentity()).thenReturn(identity); when(sone.getRequestUri()).thenReturn(requestUri); + when(sone.getTime()).thenReturn(currentTimeMillis() - DAYS.toMillis(1)); + } + + private void setupSoneAsUnknown() { + when(sone.getTime()).thenReturn(0L); } @Before @@ -342,7 +355,8 @@ public class SoneDownloaderTest { @Test public void addingASoneWillRegisterItsKey() { soneDownloader.addSone(sone); - verify(freenetInterface).registerUsk(eq(sone), any(SoneUpdater.class)); + verify(freenetInterface).registerActiveUsk(eq(sone.getRequestUri()), any( + USKCallback.class)); verify(freenetInterface, never()).unregisterUsk(sone); } @@ -350,7 +364,8 @@ public class SoneDownloaderTest { public void addingASoneTwiceWillAlsoDeregisterItsKey() { soneDownloader.addSone(sone); soneDownloader.addSone(sone); - verify(freenetInterface, times(2)).registerUsk(eq(sone), any(SoneUpdater.class)); + verify(freenetInterface, times(2)).registerActiveUsk(eq( + sone.getRequestUri()), any(USKCallback.class)); verify(freenetInterface).unregisterUsk(sone); } @@ -731,6 +746,7 @@ public class SoneDownloaderTest { @Test public void notBeingAbleToFetchAnUnknownSoneDoesNotUpdateCore() { + setupSoneAsUnknown(); soneDownloader.fetchSoneAction(sone).run(); verify(freenetInterface).fetchUri(requestUri); verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown); @@ -746,7 +762,6 @@ public class SoneDownloaderTest { @Test public void notBeingAbleToFetchAKnownSoneDoesNotUpdateCore() { - when(sone.getTime()).thenReturn(1000L); soneDownloader.fetchSoneAction(sone).run(); verify(freenetInterface).fetchUri(requestUri); verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle); @@ -755,6 +770,7 @@ public class SoneDownloaderTest { @Test(expected = NullPointerException.class) public void exceptionWhileFetchingAnUnknownSoneDoesNotUpdateCore() { + setupSoneAsUnknown(); when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class); try { soneDownloader.fetchSoneAction(sone).run(); @@ -767,7 +783,6 @@ public class SoneDownloaderTest { @Test(expected = NullPointerException.class) public void exceptionWhileFetchingAKnownSoneDoesNotUpdateCore() { - when(sone.getTime()).thenReturn(1000L); when(freenetInterface.fetchUri(requestUri)).thenThrow(NullPointerException.class); try { soneDownloader.fetchSoneAction(sone).run(); @@ -833,7 +848,7 @@ public class SoneDownloaderTest { when(freenetInterface.fetchUri(requestUri)).thenReturn(fetchResult); soneDownloader.fetchSone(sone, sone.getRequestUri(), true); verify(core, never()).updateSone(any(Sone.class)); - verifyThatSoneStatusWasChangedToDownloadingAndBackTo(unknown); + verifyThatSoneStatusWasChangedToDownloadingAndBackTo(idle); } private Fetched createFetchResult(FreenetURI uri, InputStream inputStream) throws IOException { diff --git a/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java b/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java new file mode 100644 index 0000000..8fff7bd --- /dev/null +++ b/src/test/java/net/pterodactylus/sone/freenet/KeyTest.java @@ -0,0 +1,69 @@ +package net.pterodactylus.sone.freenet; + +import static freenet.support.Base64.encode; +import static net.pterodactylus.sone.freenet.Key.from; +import static net.pterodactylus.sone.freenet.Key.routingKey; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +import java.net.MalformedURLException; + +import freenet.keys.FreenetURI; + +import org.junit.Test; + +/** + * Unit test for {@link Key}. + * + * @author David ‘Bombe’ Roden + */ +public class KeyTest { + + private final FreenetURI uri; + private final Key key; + + public KeyTest() throws MalformedURLException { + uri = new FreenetURI( + "SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/some-site-12/foo/bar.html"); + key = from(uri); + } + + @Test + public void keyCanBeCreatedFromFreenetUri() throws MalformedURLException { + assertThat(key.getRoutingKey(), + is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs")); + assertThat(key.getCryptoKey(), + is("Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI")); + assertThat(key.getExtra(), is("AQACAAE")); + } + + @Test + public void keyCanBeConvertedToUsk() throws MalformedURLException { + FreenetURI uskUri = key.toUsk("other-site", 15, "some", "path.html"); + assertThat(uskUri.toString(), + is("USK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/15/some/path.html")); + } + + @Test + public void keyCanBeConvertedToSskWithoutEdition() + throws MalformedURLException { + FreenetURI uskUri = key.toSsk("other-site", "some", "path.html"); + assertThat(uskUri.toString(), + is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site/some/path.html")); + } + + @Test + public void keyCanBeConvertedToSskWithEdition() + throws MalformedURLException { + FreenetURI uskUri = key.toSsk("other-site", 15, "some", "path.html"); + assertThat(uskUri.toString(), + is("SSK@NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs,Miglsgix0VR56ZiPl4NgjnUd~UdrnHqIvXJ3KKHmxmI,AQACAAE/other-site-15/some/path.html")); + } + + @Test + public void routingKeyIsExtractCorrectly() { + assertThat(routingKey(uri), + is("NfUYvxDwU9vqb2mh-qdT~DYJ6U0XNbxMGGoLe0aCHJs")); + } + +}