From 30f5855cafbf80bb2ca67f370eb8fd2aa893e360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilmann=20Z=C3=A4schke?= Date: Mon, 12 Aug 2024 10:47:22 +0200 Subject: [PATCH] Scenario builder for unit tests --- CHANGELOG.md | 1 + README.md | 12 +- src/main/java/org/scion/jpan/Constants.java | 9 +- .../java/org/scion/jpan/ScionService.java | 2 +- src/main/java/org/scion/jpan/ScionUtil.java | 8 + .../scion/jpan/internal/LocalTopology.java | 66 +++- .../org/scion/jpan/internal/MultiMap.java | 8 + .../jpan/internal/ScionBootstrapper.java | 20 +- .../org/scion/jpan/internal/Segments.java | 8 +- .../java/org/scion/jpan/ProtobufPathDemo.java | 4 +- .../org/scion/jpan/ProtobufSegmentDemo.java | 4 - .../org/scion/jpan/ScionBootstrapperTest.java | 4 +- .../api/DatagramChannelPathSwitchTest.java | 6 +- .../java/org/scion/jpan/api/ScionTest.java | 2 - .../scion/jpan/demo/ScmpTracerouteDemo.java | 4 +- .../scion/jpan/demo/util/ToStringUtil.java | 2 +- .../internal/AbstractSegmentsMinimalTest.java | 250 ++++---------- .../jpan/internal/SegmentsMinimal110Test.java | 2 +- .../internal/SegmentsMinimal1111Test.java | 6 +- .../jpan/internal/SegmentsMinimal111Test.java | 6 +- .../jpan/internal/SegmentsMinimal120Test.java | 2 +- .../jpan/internal/SegmentsMinimal121Test.java | 2 +- .../jpan/testutil/MockControlServer.java | 11 +- .../org/scion/jpan/testutil/Scenario.java | 312 ++++++++++++++++++ 24 files changed, 502 insertions(+), 249 deletions(-) create mode 100644 src/test/java/org/scion/jpan/testutil/Scenario.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eaede06..29c3196f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Support for bootstrapper TRC metadata. [#110](https://github.com/scionproto-contrib/jpan/pull/110) - Added `copy(...)` method for paths. [#111](https://github.com/scionproto-contrib/jpan/pull/111) +- Added Scenario builder for unit tests. [#112](https://github.com/scionproto-contrib/jpan/pull/112) ### Changed - Clean up TODO and deprecation info. [#100](https://github.com/scionproto-contrib/jpan/pull/100) diff --git a/README.md b/README.md index 19994a28..f3b79690 100644 --- a/README.md +++ b/README.md @@ -330,12 +330,12 @@ attempt to get network information in the following order until it succeeds: The reason that the daemon is checked last is that it has a default setting (`localhost:30255`) while the other options are skipped if no property or environment variable is defined. -| Option | Java property | Environment variable | Default value | -|-------------------------------------|----------------------------------|-------------------------------|-----------------| -| Daemon port | `org.scion.daemon.port` | `SCION_DAEMON` | localhost:30255 | -| Bootstrap topology file path | `org.scion.bootstrap.topoFile` | `SCION_BOOTSTRAP_TOPO_FILE` | | -| Bootstrap server host | `org.scion.bootstrap.host` | `SCION_BOOTSTRAP_HOST` | | -| Bootstrap DNS NAPTR entry host name | `org.scion.bootstrap.naptr.name` | `SCION_BOOTSTRAP_NAPTR_NAME` | | +| Option | Java property | Environment variable | Default value | +|-------------------------------------|-------------------------------------|-------------------------------|-----------------| +| Daemon port | `org.scion.daemon.port` | `SCION_DAEMON` | localhost:30255 | +| Bootstrap topology file path | `org.scion.bootstrap.topoFile` | `SCION_BOOTSTRAP_TOPO_FILE` | | +| Bootstrap server host | `org.scion.bootstrap.host` | `SCION_BOOTSTRAP_HOST` | | +| Bootstrap DNS NAPTR entry host name | `org.scion.bootstrap.naptr.name` | `SCION_BOOTSTRAP_NAPTR_NAME` | | | Bootstrap DNS NAPTR entry host name | `org.scion.test.useOsSearchDomains` | `SCION_USE_OS_SEARCH_DOMAINS` | true | ### DNS diff --git a/src/main/java/org/scion/jpan/Constants.java b/src/main/java/org/scion/jpan/Constants.java index e1cb8ed6..ff6b26df 100644 --- a/src/main/java/org/scion/jpan/Constants.java +++ b/src/main/java/org/scion/jpan/Constants.java @@ -27,16 +27,19 @@ public final class Constants { /** Address of bootstrap server (http), e.g. 192.168.42.42 */ public static final String PROPERTY_BOOTSTRAP_HOST = "org.scion.bootstrap.host"; + /** Address of bootstrap server (http), e.g. 192.168.42.42 */ public static final String ENV_BOOTSTRAP_HOST = "SCION_BOOTSTRAP_HOST"; /** Host name of DNS entry with NAPTR record for bootstrap service. */ public static final String PROPERTY_BOOTSTRAP_NAPTR_NAME = "org.scion.bootstrap.naptr.name"; + /** Host name of DNS entry with NAPTR record for bootstrap service. */ public static final String ENV_BOOTSTRAP_NAPTR_NAME = "SCION_BOOTSTRAP_NAPTR_NAME"; /** path/file name for topology file. */ public static final String PROPERTY_BOOTSTRAP_TOPO_FILE = "org.scion.bootstrap.topoFile"; + /** path/file name for topology file. */ public static final String ENV_BOOTSTRAP_TOPO_FILE = "SCION_BOOTSTRAP_TOPO_FILE"; @@ -62,12 +65,12 @@ public final class Constants { public static final int DEFAULT_PATH_EXPIRY_MARGIN = 10; /** - * Disable usage of OS search domains for DNS lookup, e.g from /etc/resolv.conf. This needs to be + * Disable usage of OS search domains for DNS lookup, e.g. from /etc/resolv.conf. This needs to be * disabled for JUnit testing. */ - public static final String PROPERTY_USE_OS_SEARCH_DOMAINS = "SCION_USE_OS_SEARCH_DOMAINS"; + public static final String PROPERTY_USE_OS_SEARCH_DOMAINS = "org.scion.test.useOsSearchDomains"; - public static final String ENV_USE_OS_SEARCH_DOMAINS = "org.scion.test.useOsSearchDomains"; + public static final String ENV_USE_OS_SEARCH_DOMAINS = "SCION_USE_OS_SEARCH_DOMAINS"; public static final boolean DEFAULT_USE_OS_SEARCH_DOMAINS = true; /** diff --git a/src/main/java/org/scion/jpan/ScionService.java b/src/main/java/org/scion/jpan/ScionService.java index 596b5253..e37b6067 100644 --- a/src/main/java/org/scion/jpan/ScionService.java +++ b/src/main/java/org/scion/jpan/ScionService.java @@ -109,7 +109,7 @@ protected ScionService(String addressOrHost, Mode mode) { } String csHost = bootstrapper.getLocalTopology().getControlServerAddress(); LOG.info("Bootstrapping with control service: {}", csHost); - localIsdAs.set(bootstrapper.getLocalTopology().getLocalIsdAs()); + localIsdAs.set(bootstrapper.getLocalTopology().getIsdAs()); // TODO InsecureChannelCredentials: Implement authentication! channel = Grpc.newChannelBuilder(csHost, InsecureChannelCredentials.create()).build(); daemonStub = null; diff --git a/src/main/java/org/scion/jpan/ScionUtil.java b/src/main/java/org/scion/jpan/ScionUtil.java index 37f204bd..89be6c2e 100644 --- a/src/main/java/org/scion/jpan/ScionUtil.java +++ b/src/main/java/org/scion/jpan/ScionUtil.java @@ -203,5 +203,13 @@ static InetSocketAddress parseInetSocketAddress(String addrStr) { } } + public static boolean isWildcard(long isdAs) { + return isdAs == toWildcard(isdAs); + } + + public static long toWildcard(long isdAs) { + return (isdAs >>> 48) << 48; + } + private ScionUtil() {} } diff --git a/src/main/java/org/scion/jpan/internal/LocalTopology.java b/src/main/java/org/scion/jpan/internal/LocalTopology.java index bd1b20df..725e59f5 100644 --- a/src/main/java/org/scion/jpan/internal/LocalTopology.java +++ b/src/main/java/org/scion/jpan/internal/LocalTopology.java @@ -18,6 +18,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import org.scion.jpan.ScionRuntimeException; @@ -51,11 +52,11 @@ public String getControlServerAddress() { return controlServices.get(0).ipString; } - public boolean isLocalAsCore() { + public boolean isCoreAs() { return isCoreAs; } - public long getLocalIsdAs() { + public long getIsdAs() { return ScionUtil.parseIA(localIsdAs); } @@ -78,7 +79,7 @@ public List getBorderRouterAddresses() { return result; } - public int getLocalMtu() { + public int getMtu() { return this.localMtu; } @@ -101,9 +102,12 @@ private void parseTopologyFile(String topologyFile) { JsonElement local = underlay.has(("local")) ? underlay.get(("local")) : underlay.get(("public")); JsonElement remote = underlay.get(("remote")); + long isdAs = ScionUtil.parseIA(ife.get("isd_as").getAsString()); + int mtu = ife.get("mtu").getAsInt(); + String linkTo = ife.get("link_to").getAsString(); interfaces.add( new BorderRouterInterface( - ifEntry.getKey(), local.getAsString(), remote.getAsString())); + ifEntry.getKey(), local.getAsString(), remote.getAsString(), isdAs, mtu, linkTo)); } borderRouters.add(new BorderRouter(e.getKey(), addr, interfaces)); } @@ -142,34 +146,76 @@ public String toString() { } public List getControlServices() { - return controlServices; + return Collections.unmodifiableList(controlServices); } - private static class BorderRouter { + public List getBorderRouters() { + return Collections.unmodifiableList(borderRouters); + } + + public static class BorderRouter { private final String name; private final String internalAddress; private final List interfaces; - public BorderRouter(String name, String addr, List interfaces) { + BorderRouter(String name, String addr, List interfaces) { this.name = name; this.internalAddress = addr; this.interfaces = interfaces; } + + public Iterable getInterfaces() { + return interfaces; + } } - private static class BorderRouterInterface { + public static class BorderRouterInterface { + public static final String PARENT = "parent"; + public static final String CHILD = "child"; + public static final String CORE = "core"; final int id; final String publicUnderlay; final String remoteUnderlay; + final long isdAs; + final int mtu; + final String linkTo; - public BorderRouterInterface(String id, String publicU, String remoteU) { + BorderRouterInterface( + String id, String publicU, String remoteU, long isdAs, int mtu, String linkTo) { this.id = Integer.parseInt(id); this.publicUnderlay = publicU; this.remoteUnderlay = remoteU; + this.isdAs = isdAs; + this.mtu = mtu; + this.linkTo = linkTo; + } + + public long getIsdAs() { + return isdAs; + } + + public int getMtu() { + return mtu; + } + + public int getId() { + return id; + } + + public String getLinkTo() { + return linkTo; + } + + public String getRemoteUnderlay() { + return remoteUnderlay; + } + + public String getPublicUnderlay() { + return publicUnderlay; } } - static class ServiceNode { + public static class ServiceNode { final String name; final String ipString; diff --git a/src/main/java/org/scion/jpan/internal/MultiMap.java b/src/main/java/org/scion/jpan/internal/MultiMap.java index c44b830b..6cee47c4 100644 --- a/src/main/java/org/scion/jpan/internal/MultiMap.java +++ b/src/main/java/org/scion/jpan/internal/MultiMap.java @@ -39,4 +39,12 @@ public boolean isEmpty() { public void clear() { map.clear(); } + + public List values() { + ArrayList values = new ArrayList<>(map.size()); + for (Map.Entry> e : map.entrySet()) { + values.addAll(e.getValue()); + } + return values; + } } diff --git a/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java b/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java index 7ec33284..6e8448e2 100644 --- a/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java +++ b/src/main/java/org/scion/jpan/internal/ScionBootstrapper.java @@ -37,22 +37,21 @@ public class ScionBootstrapper { private static final Logger LOG = LoggerFactory.getLogger(ScionBootstrapper.class.getName()); - private static final String BASE_URL = ""; private static final String TOPOLOGY_ENDPOINT = "topology"; private static final Duration httpRequestTimeout = Duration.of(2, ChronoUnit.SECONDS); private final String topologyResource; - private final LocalTopology topology; + private final LocalTopology localAS; private final GlobalTopology world; protected ScionBootstrapper(String topologyServiceAddress) { this.topologyResource = topologyServiceAddress; - this.topology = initLocal(); + this.localAS = initLocal(); this.world = initGlobal(); } protected ScionBootstrapper(java.nio.file.Path file) { this.topologyResource = file.toString(); - this.topology = this.init(file); + this.localAS = this.init(file); this.world = GlobalTopology.createEmpty(); } @@ -75,7 +74,7 @@ public static synchronized ScionBootstrapper createViaTopoFile(java.nio.file.Pat } public LocalTopology getLocalTopology() { - return topology; + return localAS; } public GlobalTopology getGlobalTopology() { @@ -95,12 +94,7 @@ private static String bootstrapViaDNS(String hostName) { } private LocalTopology initLocal() { - LocalTopology topo = LocalTopology.create(fetchFile(TOPOLOGY_ENDPOINT)); - if (topo.getControlServices().isEmpty()) { - throw new ScionRuntimeException( - "No control servers found in topology provided by " + topologyResource); - } - return topo; + return LocalTopology.create(fetchFile(TOPOLOGY_ENDPOINT)); } private GlobalTopology initGlobal() { @@ -129,7 +123,7 @@ private LocalTopology init(java.nio.file.Path file) { } LocalTopology topo = LocalTopology.create(contentBuilder.toString()); if (topo.getControlServices().isEmpty()) { - throw new ScionRuntimeException("No control service found in topology filet: " + file); + throw new ScionRuntimeException("No control service found in topology file: " + file); } return topo; } @@ -144,7 +138,7 @@ public void refreshTopology() { public String fetchFile(String resource) { try { LOG.info("Fetching resource from bootstrap server: {} {}", topologyResource, resource); - URL url = new URL("http://" + topologyResource + "/" + BASE_URL + resource); + URL url = new URL("http://" + topologyResource + "/" + resource); return fetchFile(url); } catch (IOException e) { throw new ScionRuntimeException( diff --git a/src/main/java/org/scion/jpan/internal/Segments.java b/src/main/java/org/scion/jpan/internal/Segments.java index 93f8e25b..01c894ae 100644 --- a/src/main/java/org/scion/jpan/internal/Segments.java +++ b/src/main/java/org/scion/jpan/internal/Segments.java @@ -175,7 +175,7 @@ public static List getPaths( if (srcIsdAs == dstIsdAs) { // case A: same AS, return empty path Daemon.Path.Builder path = Daemon.Path.newBuilder(); - path.setMtu(localAS.getLocalMtu()); + path.setMtu(localAS.getMtu()); path.setExpiration(Timestamp.newBuilder().setSeconds(Instant.now().getEpochSecond()).build()); return Collections.singletonList(path.build()); } @@ -189,7 +189,7 @@ public static List getPaths( long to = dstIsdAs; List> segments = new ArrayList<>(); // First, if necessary, try to get UP segments - if (!localAS.isLocalAsCore()) { + if (!localAS.isCoreAs()) { // get UP segments // TODO find out if dstIsAs is core and directly ask for it. List segmentsUp = getSegments(segmentStub, srcIsdAs, srcWildcard); @@ -421,7 +421,7 @@ private static Daemon.Path buildPath(LocalTopology localAS, Seg.PathSegment... s // info fields boolean[] reversed = new boolean[segments.length]; - long startIA = localAS.getLocalIsdAs(); + long startIA = localAS.getIsdAs(); final ByteUtil.MutLong endingIA = new ByteUtil.MutLong(-1); for (int i = 0; i < infos.length; i++) { reversed[i] = isReversed(segments[i], startIA, endingIA); @@ -430,7 +430,7 @@ private static Daemon.Path buildPath(LocalTopology localAS, Seg.PathSegment... s } // hop fields - path.setMtu(localAS.getLocalMtu()); + path.setMtu(localAS.getMtu()); for (int i = 0; i < segments.length; i++) { // bytePosSegID: 6 = 4 bytes path head + 2 byte flag in first info field writeHopFields(path, raw, 6 + i * 8, segments[i], reversed[i], infos[i]); diff --git a/src/test/java/org/scion/jpan/ProtobufPathDemo.java b/src/test/java/org/scion/jpan/ProtobufPathDemo.java index 7122746d..b3cefaea 100644 --- a/src/test/java/org/scion/jpan/ProtobufPathDemo.java +++ b/src/test/java/org/scion/jpan/ProtobufPathDemo.java @@ -32,12 +32,12 @@ public class ProtobufPathDemo { public static void main(String[] args) { try (Scion.CloseableService daemon = - Scion.newServiceWithDaemon(DemoConstants.daemon110_minimal)) { + Scion.newServiceWithDaemon(DemoConstants.daemon1111_minimal)) { ProtobufPathDemo demo = new ProtobufPathDemo(daemon); demo.testAsInfo(); demo.testInterfaces(); demo.testServices(); - demo.testPathsDaemon(DemoConstants.ia110, DemoConstants.ia211); + demo.testPathsDaemon(DemoConstants.ia1111, DemoConstants.ia1112); // demo.testPathsControlService(srcIA, dstIA); } catch (IOException e) { throw new RuntimeException(e); diff --git a/src/test/java/org/scion/jpan/ProtobufSegmentDemo.java b/src/test/java/org/scion/jpan/ProtobufSegmentDemo.java index 8c6cd86a..653f5e9d 100644 --- a/src/test/java/org/scion/jpan/ProtobufSegmentDemo.java +++ b/src/test/java/org/scion/jpan/ProtobufSegmentDemo.java @@ -46,10 +46,6 @@ public static void main(String[] args) throws ScionException { // demo.getSegments(toWildcard(ia120), toWildcard(ia210)); } - private static long toWildcard(long ia) { - return (ia >>> 48) << 48; - } - public ProtobufSegmentDemo(String csAddress) { channel = Grpc.newChannelBuilder(csAddress, InsecureChannelCredentials.create()).build(); segmentStub = SegmentLookupServiceGrpc.newBlockingStub(channel); diff --git a/src/test/java/org/scion/jpan/ScionBootstrapperTest.java b/src/test/java/org/scion/jpan/ScionBootstrapperTest.java index 6c3a646a..a380b501 100644 --- a/src/test/java/org/scion/jpan/ScionBootstrapperTest.java +++ b/src/test/java/org/scion/jpan/ScionBootstrapperTest.java @@ -36,8 +36,8 @@ void testTiny110() { ScionBootstrapper sb = ScionBootstrapper.createViaTopoFile(topoFile); LocalTopology topo = sb.getLocalTopology(); - assertEquals(ScionUtil.parseIA("1-ff00:0:110"), topo.getLocalIsdAs()); + assertEquals(ScionUtil.parseIA("1-ff00:0:110"), topo.getIsdAs()); assertEquals("127.0.0.11:31000", topo.getControlServerAddress()); - assertTrue(topo.isLocalAsCore()); + assertTrue(topo.isCoreAs()); } } diff --git a/src/test/java/org/scion/jpan/api/DatagramChannelPathSwitchTest.java b/src/test/java/org/scion/jpan/api/DatagramChannelPathSwitchTest.java index ebb0b4be..74779776 100644 --- a/src/test/java/org/scion/jpan/api/DatagramChannelPathSwitchTest.java +++ b/src/test/java/org/scion/jpan/api/DatagramChannelPathSwitchTest.java @@ -54,7 +54,11 @@ void test() { PingPongChannelHelper.Client clientFn = this::client; PingPongChannelHelper pph = new PingPongChannelHelper(1, 2, 10); pph.runPingPong(serverFn, clientFn, false); - assertEquals(2 * 10, MockNetwork.getForwardCount(0)); + // TODO: This sometimes reports 22 i.o. 20. + assertEquals( + 2 * 10, + MockNetwork.getForwardCount(0), + "Actual: " + MockNetwork.getForwardCount(0) + "/" + MockNetwork.getForwardCount(1)); assertEquals(2 * 10, MockNetwork.getForwardCount(1)); assertEquals(2 * 2 * 10, MockNetwork.getAndResetForwardCount()); } diff --git a/src/test/java/org/scion/jpan/api/ScionTest.java b/src/test/java/org/scion/jpan/api/ScionTest.java index 3baf6e57..d93face8 100644 --- a/src/test/java/org/scion/jpan/api/ScionTest.java +++ b/src/test/java/org/scion/jpan/api/ScionTest.java @@ -299,7 +299,6 @@ void newServiceWithDNS() throws IOException { assertNotNull(paths); assertFalse(paths.isEmpty()); assertEquals(1, MockNetwork.getTopoServer().getAndResetCallCount()); - assertEquals(1, MockNetwork.getControlServer().getAndResetCallCount()); assertNotEquals(Scion.defaultService(), ss); } finally { MockNetwork.stopTiny(); @@ -319,7 +318,6 @@ void newServiceWithBootstrapServer() throws IOException { assertNotNull(paths); assertFalse(paths.isEmpty()); assertEquals(1, MockNetwork.getTopoServer().getAndResetCallCount()); - assertEquals(1, MockNetwork.getControlServer().getAndResetCallCount()); // No DNS, no daemon, no ENV variables -> fail. assertThrows(ScionRuntimeException.class, Scion::defaultService); } finally { diff --git a/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java b/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java index 997bd8de..0c44e9d8 100644 --- a/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java +++ b/src/test/java/org/scion/jpan/demo/ScmpTracerouteDemo.java @@ -85,7 +85,9 @@ public static void main(String[] args) throws IOException { { // Local port must be 30041 for networks that expect a dispatcher ScmpTracerouteDemo demo = new ScmpTracerouteDemo(Constants.SCMP_PORT); - demo.runDemo(DemoConstants.iaAnapayaHK); + demo.runDemo(ScionUtil.parseIA("64-2:0:44")); // VEX + // demo.runDemo(ScionUtil.parseIA("66-2:0:10")); //singapore + // demo.runDemo(DemoConstants.iaAnapayaHK); // demo.runDemo(DemoConstants.iaOVGU); break; } diff --git a/src/test/java/org/scion/jpan/demo/util/ToStringUtil.java b/src/test/java/org/scion/jpan/demo/util/ToStringUtil.java index 317d6dc2..78eeddf8 100644 --- a/src/test/java/org/scion/jpan/demo/util/ToStringUtil.java +++ b/src/test/java/org/scion/jpan/demo/util/ToStringUtil.java @@ -104,7 +104,7 @@ public static String path(byte[] raw) { } else { sb.append(hfE.getIngress()).append(">").append(hfI.getEgress()); } - if (i < ph.getHopCount() - 1) { + if (i < ph.getHopCount() - 2) { sb.append(" "); } } diff --git a/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java b/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java index 997f60ef..7160c3d8 100644 --- a/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java +++ b/src/test/java/org/scion/jpan/internal/AbstractSegmentsMinimalTest.java @@ -17,33 +17,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; -import com.google.protobuf.ByteString; -import com.google.protobuf.Timestamp; import java.nio.ByteBuffer; -import java.time.Instant; import java.util.Arrays; import org.scion.jpan.ScionUtil; import org.scion.jpan.proto.control_plane.Seg; -import org.scion.jpan.proto.crypto.Signed; import org.scion.jpan.proto.daemon.Daemon; import org.scion.jpan.testutil.MockControlServer; +import org.scion.jpan.testutil.Scenario; -/** - * Test cases:
- * A (-): srcISD == dstISD; src == dst; (same ISD, same AS)
- * B (UP): srcISD == dstISD; dst == core; (same ISD, dst is core)
- * C (DOWN): srcISD == dstISD; src == core; (same ISD, dst is core)
- * D (CORE): srcISD == dstISD; src == core, dst == core; (same ISD, src/dst are cores)
- * E (UP, DOWN): srcISD == dstISD; (same ISD, src/dst are non-cores)
- * F (UP, CORE): srcISD != dstISD; dst == core; (different ISDs, dst is core)
- * G (CORE, DOWN): srcISD != dstISD; src == core; (different ISDs, src is cores)
- * H (UP, CORE, DOWN): srcISD != dstISD; (different ISDs, src/dst are non-cores)
- * I (CORE): srcISD != dstISD; (different ISDs, src/dst are cores) - */ public abstract class AbstractSegmentsMinimalTest { protected static final String AS_HOST = "my-as-host.org"; protected static final String CFG_MINIMAL = "topologies/minimal/"; - protected static final long ZERO = ScionUtil.parseIA("0-0:0:0"); + protected static final String CFG_DEFAULT = "topologies/scionproto-default/"; /** ISD 1 - core AS */ protected static final long AS_110 = ScionUtil.parseIA("1-ff00:0:110"); @@ -76,6 +61,15 @@ public abstract class AbstractSegmentsMinimalTest { protected static final long AS_211 = ScionUtil.parseIA("2-ff00:0:211"); protected static MockControlServer controlServer; + protected final Scenario scenario; + + protected AbstractSegmentsMinimalTest() { + this(CFG_MINIMAL); + } + + protected AbstractSegmentsMinimalTest(String cfg) { + scenario = Scenario.readFrom(cfg); + } protected static void checkMetaHeader( ByteBuffer rawBB, int hopCount0, int hopCount1, int hopCount2) { @@ -86,10 +80,10 @@ protected static void checkMetaHeader( protected static void checkInfo(ByteBuffer rawBB, int segmentId, int flags) { assertEquals(flags, rawBB.get()); // Info0 flags assertEquals(0, rawBB.get()); // Info0 etc - // TODO fix -> XOR SegID! if (flags != 0) { assertEquals(segmentId, ByteUtil.toUnsigned(rawBB.getShort())); // Info0 SegID } else { + // TODO fix -> XOR SegID! -> assert! rawBB.getShort(); } assertNotEquals(0, rawBB.getInt()); // Info0 timestamp @@ -152,56 +146,6 @@ protected static void checkRaw(byte[] exp, byte[] act) { assertEquals(ofs, act.length); } - private static Seg.HopField buildHopField(int expiry, int ingress, int egress) { - ByteString mac = ByteString.copyFrom(new byte[] {1, 2, 3, 4, 5, 6}); - return Seg.HopField.newBuilder() - .setExpTime(expiry) - .setIngress(ingress) - .setEgress(egress) - .setMac(mac) - .build(); - } - - private static Seg.HopEntry buildHopEntry(int mtu, Seg.HopField hf) { - return Seg.HopEntry.newBuilder().setIngressMtu(mtu).setHopField(hf).build(); - } - - private static Seg.ASEntry buildASEntry(long isdAs, long nextIA, int mtu, Seg.HopEntry he) { - Signed.Header header = - Signed.Header.newBuilder() - .setSignatureAlgorithm(Signed.SignatureAlgorithm.SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256) - .setTimestamp(now()) - .build(); - Seg.ASEntrySignedBody body = - Seg.ASEntrySignedBody.newBuilder() - .setIsdAs(isdAs) - .setNextIsdAs(nextIA) - .setMtu(mtu) - .setHopEntry(he) - .build(); - Signed.HeaderAndBodyInternal habi = - Signed.HeaderAndBodyInternal.newBuilder() - .setHeader(header.toByteString()) - .setBody(body.toByteString()) - .build(); - Signed.SignedMessage sm = - Signed.SignedMessage.newBuilder().setHeaderAndBody(habi.toByteString()).build(); - return Seg.ASEntry.newBuilder().setSigned(sm).build(); - } - - private static Seg.PathSegment buildPath(int id, Seg.ASEntry... entries) { - long now = Instant.now().getEpochSecond(); - Seg.SegmentInformation info = - Seg.SegmentInformation.newBuilder().setSegmentId(id).setTimestamp(now).build(); - Seg.PathSegment.Builder builder = - Seg.PathSegment.newBuilder().setSegmentInfo(info.toByteString()); - // for (Seg.ASEntry entry : entries) { - // builder.addAsEntries(entry); - // } - builder.addAllAsEntries(Arrays.asList(entries)); - return builder.build(); - } - private static Seg.SegmentsResponse buildResponse( Seg.SegmentType type, Seg.PathSegment... paths) { Seg.SegmentsResponse.Builder replyBuilder = Seg.SegmentsResponse.newBuilder(); @@ -211,20 +155,12 @@ private static Seg.SegmentsResponse buildResponse( return replyBuilder.build(); } - private static Timestamp now() { - Instant now = Instant.now(); - // TODO correct? Set nanos? - return Timestamp.newBuilder().setSeconds(now.getEpochSecond()).setNanos(now.getNano()).build(); - } - protected void addResponses() { addResponse110_1111(); addResponse110_1112(); addResponse110_1121(); addResponse120_210(); - // TODO redo the following ones? - // addResponse111_110(); addResponse110_111(); addResponse110_112(); addResponse110_120(); @@ -233,6 +169,35 @@ protected void addResponses() { addResponse210_211(); } + protected void addResponsesScionprotoDefault() { + long AS_130 = ScionUtil.parseIA("1-ff00:0:130"); + + addUpDown(AS_111, AS_130); + addUpDown(AS_111, AS_120); + + addUpDown(AS_112, AS_130); + addUpDown(AS_112, AS_120); + + addCore(AS_130, AS_120); + addCore(AS_120, AS_130); + } + + private void addCore(long local, long origin) { + for (Seg.PathSegment path : scenario.getSegments(local, origin)) { + controlServer.addResponse( + local, true, origin, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path)); + } + } + + private void addUpDown(long leaf, long core) { + for (Seg.PathSegment path : scenario.getSegments(leaf, core)) { + controlServer.addResponse( + leaf, false, core, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_UP, path)); + controlServer.addResponse( + core, true, leaf, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path)); + } + } + private void addResponse110_111() { // Requesting segments: 1-ff00:0:111 -> 1-0:0:0 // SEG: key=SEGMENT_TYPE_UP -> n=1 @@ -249,52 +214,25 @@ private void addResponse110_111() { // HopEntry: true mtu=1472 // HopField: exp=63 ingress=111 egress=0 - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 2)); - Seg.ASEntry ase00 = buildASEntry(AS_110, AS_111, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 111, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_111, ZERO, 1472, he01); - Seg.PathSegment path0 = buildPath(18215, ase00, ase01); + // Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 2)); + // Seg.ASEntry ase00 = buildASEntry(AS_110, AS_111, 1472, he00); + // Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 111, 0)); + // Seg.ASEntry ase01 = buildASEntry(AS_111, ZERO, 1472, he01); + // Seg.PathSegment path0 = buildPath(18215, ase00, ase01); + Seg.PathSegment path0 = scenario.getSegments(AS_111, AS_110).get(0); controlServer.addResponse( AS_111, false, AS_110, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_UP, path0)); controlServer.addResponse( AS_110, true, AS_111, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); } - // private void addResponse110_111() { - // // Requesting segments: 1-ff00:0:110 -> 1-ff00:0:111 - // // SEG: key=SEGMENT_TYPE_DOWN -> n=1 - // // PathSeg: size=10 - // // SegInfo: ts=2024-01-05T12:53:41Z id=50986 - // // AS: signed=93 signature size=71 - // // AS header: SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256 - // time=2024-01-05T12:53:41.919523006Z - // // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:111 mtu=1472 - // // HopEntry: true mtu=0 - // // HopField: exp=63 ingress=0 egress=2 - // // AS: signed=89 signature size=71 - // // AS header: SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256 - // time=2024-01-05T12:53:43.935089190Z - // // AS Body: IA=1-ff00:0:111 nextIA=0-0:0:0 mtu=1472 - // // HopEntry: true mtu=1472 - // // HopField: exp=63 ingress=111 egress=0 - // - // Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 2)); - // Seg.ASEntry ase00 = buildASEntry(AS_110, AS_111, 0, he00); - // Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 111, 0)); - // Seg.ASEntry ase01 = buildASEntry(AS_111, ZERO, 1472, he01); - // Seg.PathSegment path0 = buildPath(50986, ase00, ase01); - // - // controlServer.addResponse( - // AS_110, true, AS_111, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); - // } - private void addResponse110_1111() { // Requesting segments: 1-ff00:0:110 -> 1-ff00:0:1111 // SEG: key=SEGMENT_TYPE_DOWN -> n=1 // PathSeg: size=9 // SegInfo: ts=2024-01-10T15:58:22Z id=10619 - // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:111 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:111 mtu=1460 // HopEntry: true mtu=0 // HopField: exp=63 ingress=0 egress=2 // AS Body: IA=1-ff00:0:111 nextIA=1-ff00:0:1111 mtu=1472 @@ -303,15 +241,7 @@ private void addResponse110_1111() { // AS Body: IA=1-ff00:0:1111 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=123 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 2)); - Seg.ASEntry ase00 = buildASEntry(AS_110, AS_111, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 111, 1111)); - Seg.ASEntry ase01 = buildASEntry(AS_111, AS_1111, 1472, he01); - Seg.HopEntry he02 = buildHopEntry(1472, buildHopField(63, 123, 0)); - Seg.ASEntry ase02 = buildASEntry(AS_1111, ZERO, 1472, he02); - Seg.PathSegment path0 = buildPath(10619, ase00, ase01, ase02); - + Seg.PathSegment path0 = scenario.getSegments(AS_1111, AS_110).get(0); controlServer.addResponse( AS_110, true, AS_1111, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); controlServer.addResponse( @@ -323,7 +253,7 @@ private void addResponse110_1112() { // SEG: key=SEGMENT_TYPE_DOWN -> n=1 // PathSeg: size=10 // SegInfo: ts=2024-01-10T17:11:53Z id=25161 - // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:111 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:111 mtu=1460 // HopEntry: true mtu=0 // HopField: exp=63 ingress=0 egress=2 // AS Body: IA=1-ff00:0:111 nextIA=1-ff00:0:1112 mtu=1472 @@ -332,15 +262,7 @@ private void addResponse110_1112() { // AS Body: IA=1-ff00:0:1112 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=234 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 2)); - Seg.ASEntry ase00 = buildASEntry(AS_110, AS_111, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 111, 1112)); - Seg.ASEntry ase01 = buildASEntry(AS_111, AS_1112, 1472, he01); - Seg.HopEntry he02 = buildHopEntry(1472, buildHopField(63, 234, 0)); - Seg.ASEntry ase02 = buildASEntry(AS_1112, ZERO, 1472, he02); - Seg.PathSegment path0 = buildPath(25161, ase00, ase01, ase02); - + Seg.PathSegment path0 = scenario.getSegments(AS_1112, AS_110).get(0); controlServer.addResponse( AS_110, true, AS_1112, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); controlServer.addResponse( @@ -352,7 +274,7 @@ private void addResponse110_1121() { // SEG: key=SEGMENT_TYPE_DOWN -> n=1 // PathSeg: size=9 // SegInfo: ts=2024-01-10T17:17:43Z id=2700 - // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:112 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:112 mtu=1460 // HopEntry: true mtu=0 // HopField: exp=63 ingress=0 egress=3 // AS Body: IA=1-ff00:0:112 nextIA=1-ff00:0:1121 mtu=1450 @@ -361,15 +283,7 @@ private void addResponse110_1121() { // AS Body: IA=1-ff00:0:1121 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=345 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 3)); - Seg.ASEntry ase00 = buildASEntry(AS_110, AS_112, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 453, 1121)); - Seg.ASEntry ase01 = buildASEntry(AS_112, AS_1121, 1450, he01); - Seg.HopEntry he02 = buildHopEntry(1472, buildHopField(63, 345, 0)); - Seg.ASEntry ase02 = buildASEntry(AS_1121, ZERO, 1472, he02); - Seg.PathSegment path0 = buildPath(2700, ase00, ase01, ase02); - + Seg.PathSegment path0 = scenario.getSegments(AS_1121, AS_110).get(0); controlServer.addResponse( AS_110, true, AS_1121, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); controlServer.addResponse( @@ -384,7 +298,7 @@ private void addResponse110_112() { // SegInfo: ts=2024-01-05T15:02:17Z id=5701 // AS: signed=93 signature size=71 // AS header: SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256 time=2024-01-05T15:02:17.455400479Z - // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:112 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=1-ff00:0:112 mtu=1460 // HopEntry: true mtu=0 // HopField: exp=63 ingress=0 egress=3 // AS: signed=90 signature size=71 @@ -392,12 +306,7 @@ private void addResponse110_112() { // AS Body: IA=1-ff00:0:112 nextIA=0-0:0:0 mtu=1450 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=453 egress=0 - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 3)); - Seg.ASEntry ase00 = buildASEntry(AS_110, AS_112, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 453, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_112, ZERO, 1450, he01); - Seg.PathSegment path0 = buildPath(5701, ase00, ase01); - + Seg.PathSegment path0 = scenario.getSegments(AS_112, AS_110).get(0); controlServer.addResponse( AS_110, true, AS_112, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); } @@ -414,16 +323,10 @@ private void addResponse110_120() { // HopField: exp=63 ingress=0 egress=10 // AS: signed=89 signature size=70 // AS header: SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256 time=2024-01-10T12:48:17.672710479Z - // AS Body: IA=1-ff00:0:110 nextIA=0-0:0:0 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=0-0:0:0 mtu=1460 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=1 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 10)); - Seg.ASEntry ase00 = buildASEntry(AS_120, AS_110, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 1, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_110, ZERO, 1472, he01); - Seg.PathSegment path0 = buildPath(26755, ase00, ase01); - + Seg.PathSegment path0 = scenario.getSegments(AS_110, AS_120).get(0); controlServer.addResponse( AS_110, true, AS_120, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path0)); } @@ -443,13 +346,7 @@ private void addResponse120_121() { // AS Body: IA=1-ff00:0:121 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=104 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 21)); - Seg.ASEntry ase00 = buildASEntry(AS_120, AS_121, 1472, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 104, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_121, ZERO, 1472, he01); - Seg.PathSegment path0 = buildPath(48280, ase00, ase01); - + Seg.PathSegment path0 = scenario.getSegments(AS_121, AS_120).get(0); controlServer.addResponse( AS_120, true, AS_121, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); controlServer.addResponse( @@ -473,17 +370,12 @@ private void addResponse120_210() { // AS Body: IA=1-ff00:0:120 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=210 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 105)); - Seg.ASEntry ase00 = buildASEntry(AS_210, AS_120, 1280, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 210, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_120, ZERO, 1472, he01); - Seg.PathSegment path0 = buildPath(18204, ase00, ase01); - + Seg.PathSegment path0 = scenario.getSegments(AS_120, AS_210).get(0); + Seg.PathSegment path1 = scenario.getSegments(AS_210, AS_120).get(0); controlServer.addResponse( AS_120, true, AS_210, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path0)); controlServer.addResponse( - AS_210, true, AS_120, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path0)); + AS_210, true, AS_120, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path1)); } private void addResponse110_210() { @@ -503,18 +395,10 @@ private void addResponse110_210() { // HopField: exp=63 ingress=210 egress=10 // AS: signed=88 signature size=70 // AS header: SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256 time=2024-01-10T12:59:51.171109823Z - // AS Body: IA=1-ff00:0:110 nextIA=0-0:0:0 mtu=1472 + // AS Body: IA=1-ff00:0:110 nextIA=0-0:0:0 mtu=1460 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=1 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 105)); - Seg.ASEntry ase00 = buildASEntry(AS_210, AS_120, 1280, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 210, 10)); - Seg.ASEntry ase01 = buildASEntry(AS_120, AS_110, 1472, he01); - Seg.HopEntry he02 = buildHopEntry(1472, buildHopField(63, 1, 0)); - Seg.ASEntry ase02 = buildASEntry(AS_110, ZERO, 1472, he02); - Seg.PathSegment path0 = buildPath(15767, ase00, ase01, ase02); - + Seg.PathSegment path0 = scenario.getSegments(AS_110, AS_210).get(0); controlServer.addResponse( AS_110, true, AS_210, true, buildResponse(Seg.SegmentType.SEGMENT_TYPE_CORE, path0)); } @@ -534,13 +418,7 @@ private void addResponse210_211() { // AS Body: IA=2-ff00:0:211 nextIA=0-0:0:0 mtu=1472 // HopEntry: true mtu=1472 // HopField: exp=63 ingress=503 egress=0 - - Seg.HopEntry he00 = buildHopEntry(0, buildHopField(63, 0, 450)); - Seg.ASEntry ase00 = buildASEntry(AS_210, AS_211, 1280, he00); - Seg.HopEntry he01 = buildHopEntry(1472, buildHopField(63, 503, 0)); - Seg.ASEntry ase01 = buildASEntry(AS_211, ZERO, 1472, he01); - Seg.PathSegment path0 = buildPath(15299, ase00, ase01); - + Seg.PathSegment path0 = scenario.getSegments(AS_211, AS_210).get(0); controlServer.addResponse( AS_210, true, AS_211, false, buildResponse(Seg.SegmentType.SEGMENT_TYPE_DOWN, path0)); controlServer.addResponse( diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java index 2a83fbd2..fc98f40a 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal110Test.java @@ -175,7 +175,7 @@ void caseF0_SameIsd_CoreDown() throws IOException { ByteBuffer rawBB = path.getRaw().asReadOnlyByteBuffer(); checkMetaHeader(rawBB, 2, 2, 0); checkInfo(rawBB, 10619, 0); - checkInfo(rawBB, 48280, 1); + checkInfo(rawBB, 10021, 1); checkHopField(rawBB, 1, 0); checkHopField(rawBB, 0, 10); checkHopField(rawBB, 0, 21); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java index ea88668e..92583552 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal1111Test.java @@ -122,11 +122,7 @@ void caseB_SameIsd_Up() throws IOException { -90, -67, 121 }; - // System.out.println(ToStringUtil.pathLong(raw)); // TODO - // System.out.println(ToStringUtil.path(raw)); // TODO Daemon.Path path = paths.get(0); - // System.out.println(ToStringUtil.path(path.getRaw().toByteArray())); // TODO - // System.out.println(ToStringUtil.pathLong(path.getRaw().toByteArray())); // TODO ByteBuffer rawBB = path.getRaw().asReadOnlyByteBuffer(); checkMetaHeader(rawBB, 3, 0, 0); checkInfo(rawBB, 10619, 0); @@ -252,7 +248,7 @@ void caseE_SameIsd_UpDown_OneCoreAS_b() throws IOException { ByteBuffer rawBB = path.getRaw().asReadOnlyByteBuffer(); checkMetaHeader(rawBB, 3, 3, 0); checkInfo(rawBB, 10619, 0); - checkInfo(rawBB, 2700, 1); + checkInfo(rawBB, 10003, 1); checkHopField(rawBB, 123, 0); checkHopField(rawBB, 111, 1111); checkHopField(rawBB, 0, 2); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java index 8fbe471c..57dec59b 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal111Test.java @@ -174,7 +174,7 @@ void caseE_SameIsd_UpDown_OneCoreAS() throws IOException { ByteBuffer rawBB = path.getRaw().asReadOnlyByteBuffer(); checkMetaHeader(rawBB, 2, 2, 0); checkInfo(rawBB, 18215, 0); - checkInfo(rawBB, 5701, 1); + checkInfo(rawBB, 10003, 1); checkHopField(rawBB, 111, 0); checkHopField(rawBB, 0, 2); checkHopField(rawBB, 0, 3); @@ -233,7 +233,7 @@ void caseE_SameIsd_UpDownTwoCoreAS() throws IOException { checkMetaHeader(rawBB, 2, 2, 2); checkInfo(rawBB, 18215, 0); checkInfo(rawBB, 26755, 0); - checkInfo(rawBB, 48280, 1); + checkInfo(rawBB, 10021, 1); checkHopField(rawBB, 111, 0); checkHopField(rawBB, 0, 2); checkHopField(rawBB, 1, 0); @@ -414,7 +414,7 @@ void caseH_DifferentIsd_UpCoreDown_2_Hop() throws IOException { checkMetaHeader(rawBB, 2, 3, 2); checkInfo(rawBB, 18215, 0); checkInfo(rawBB, 15767, 0); - checkInfo(rawBB, 15299, 1); + checkInfo(rawBB, 10450, 1); checkHopField(rawBB, 111, 0); checkHopField(rawBB, 0, 2); checkHopField(rawBB, 1, 0); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java index 85c34dea..07e884d8 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal120Test.java @@ -108,7 +108,7 @@ void caseG_DifferentIsd_CoreDown_1_Hop() throws IOException { ByteBuffer rawBB = path.getRaw().asReadOnlyByteBuffer(); checkMetaHeader(rawBB, 2, 2, 0); checkInfo(rawBB, 10619, 0); - checkInfo(rawBB, 15299, 1); + checkInfo(rawBB, 10450, 1); checkHopField(rawBB, 210, 0); checkHopField(rawBB, 0, 105); checkHopField(rawBB, 0, 450); diff --git a/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java b/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java index 3cedf3bc..c1331c6a 100644 --- a/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java +++ b/src/test/java/org/scion/jpan/internal/SegmentsMinimal121Test.java @@ -170,7 +170,7 @@ void caseH_DifferentIsd_UpCoreDown_1_Hop() throws IOException { checkMetaHeader(rawBB, 2, 2, 2); checkInfo(rawBB, 18215, 0); checkInfo(rawBB, 15767, 0); - checkInfo(rawBB, 15299, 1); + checkInfo(rawBB, 10450, 1); checkHopField(rawBB, 104, 0); checkHopField(rawBB, 0, 21); checkHopField(rawBB, 210, 0); diff --git a/src/test/java/org/scion/jpan/testutil/MockControlServer.java b/src/test/java/org/scion/jpan/testutil/MockControlServer.java index c4fe9c73..f471e140 100644 --- a/src/test/java/org/scion/jpan/testutil/MockControlServer.java +++ b/src/test/java/org/scion/jpan/testutil/MockControlServer.java @@ -77,7 +77,7 @@ private MockControlServer startInternal() throws IOException { .addService(controlServer) .build() .start(); - logger.info("Server started, listening on " + address); + logger.info("Server started, listening on {}", address); Runtime.getRuntime() .addShutdownHook( @@ -131,7 +131,7 @@ public void segments( Seg.SegmentsRequest req, StreamObserver responseObserver) { String srcIsdAsStr = ScionUtil.toStringIA(req.getSrcIsdAs()); String dstIsdAsStr = ScionUtil.toStringIA(req.getDstIsdAs()); - logger.info("Segment request: " + srcIsdAsStr + " -> " + dstIsdAsStr); + logger.info("Segment request: {} -> {}", srcIsdAsStr, dstIsdAsStr); callCount.incrementAndGet(); if (responses.isEmpty()) { @@ -200,6 +200,13 @@ private String key(long ia0, long ia1) { private static Seg.SegmentsResponse defaultResponse(long srcIA, long dstIA) { Seg.SegmentsResponse.Builder replyBuilder = Seg.SegmentsResponse.newBuilder(); + if (srcIA == dstIA) { + // Single core AS, no paths are available + return replyBuilder.build(); + } + // Wildcards are only used for CORE AS, and there is only one core AS in "tiny": 1-ff00:0:110 + srcIA = ScionUtil.isWildcard(srcIA) ? ScionUtil.parseIA("1-ff00:0:110") : srcIA; + dstIA = ScionUtil.isWildcard(dstIA) ? ScionUtil.parseIA("1-ff00:0:110") : dstIA; ByteString mac0 = ByteString.copyFrom(new byte[] {1, 2, 3, 4, 5, 6}); Seg.HopField hop0 = Seg.HopField.newBuilder().setMac(mac0).setIngress(3).setEgress(2).build(); diff --git a/src/test/java/org/scion/jpan/testutil/Scenario.java b/src/test/java/org/scion/jpan/testutil/Scenario.java new file mode 100644 index 00000000..94b722db --- /dev/null +++ b/src/test/java/org/scion/jpan/testutil/Scenario.java @@ -0,0 +1,312 @@ +// Copyright 2024 ETH Zurich +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.scion.jpan.testutil; + +import static org.scion.jpan.internal.LocalTopology.BorderRouter; +import static org.scion.jpan.internal.LocalTopology.BorderRouterInterface; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.Timestamp; +import java.io.File; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.scion.jpan.ScionRuntimeException; +import org.scion.jpan.ScionUtil; +import org.scion.jpan.internal.LocalTopology; +import org.scion.jpan.proto.control_plane.Seg; +import org.scion.jpan.proto.crypto.Signed; + +public class Scenario { + + protected static final long ZERO = ScionUtil.parseIA("0-0:0:0"); + private static final Map scenarios = new ConcurrentHashMap<>(); + private final Map daemons = new HashMap<>(); + private final Map topologies = new HashMap<>(); + private final List segmentDb = new ArrayList<>(); + + private static class SegmentEntry { + final long localAS; + final long originAS; + final boolean isCoreSegment; + Seg.PathSegment segment; + + SegmentEntry(long localAS, long originAS, Seg.PathSegment segment, boolean isCoreSegment) { + this.localAS = localAS; + this.originAS = originAS; + this.isCoreSegment = isCoreSegment; + this.segment = segment; + } + } + + public static Scenario readFrom(String pathName) { + return readFrom(Paths.get(pathName)); + } + + public static Scenario readFrom(Path pathName) { + Path resolved = resolve(pathName); + return scenarios.computeIfAbsent(resolved.toString(), path -> new Scenario(resolved)); + } + + private static Path resolve(Path pathName) { + if (!Files.exists(pathName)) { + // fallback, try resource folder + ClassLoader classLoader = Scenario.class.getClassLoader(); + URL resource = classLoader.getResource(pathName.toString()); + if (resource != null) { + try { + return Paths.get(resource.toURI()); + } catch (URISyntaxException e) { + throw new ScionRuntimeException(e); + } + } + } + return pathName; + } + + public InetSocketAddress getControlServer(long isdAs) { + LocalTopology topo = topologies.get(isdAs); + String addr = topo.getControlServerAddress(); + String ip = addr.substring(0, addr.indexOf(':')); + String port = addr.substring(addr.indexOf(':') + 1); + return new InetSocketAddress(ip, Integer.parseInt(port)); + } + + public String getDaemon(long isdAs) { + return daemons.get(isdAs) + ":30255"; + } + + public List getSegments(long srcIsdAs, long dstIsdAs) { + List result = new ArrayList<>(); + for (SegmentEntry se : segmentDb) { + if (se.localAS == srcIsdAs && se.originAS == dstIsdAs) { + result.add(se.segment); + } + } + return result; + } + + private Scenario(Path file) { + File parent = file.toFile(); + if (!parent.isDirectory()) { + throw new IllegalStateException(); + } + + Path addresses = Paths.get(parent.getPath(), "sciond_addresses.json"); + parseSciondAddresses(readFile(addresses)); + + try { + Files.list(file) + .filter(path -> path.getFileName().toString().startsWith("AS")) + .map(path -> Paths.get(path.toString(), "topology.json")) + .map(topoFile -> LocalTopology.create(readFile(topoFile))) + .forEach(topo -> topologies.put(topo.getIsdAs(), topo)); + } catch (IOException e) { + throw new ScionRuntimeException(e); + } + + buildSegments(); + } + + private static String readFile(Path file) { + StringBuilder contentBuilder = new StringBuilder(); + try (Stream stream = Files.lines(file, StandardCharsets.UTF_8)) { + stream.forEach(s -> contentBuilder.append(s).append("\n")); + } catch (IOException e) { + throw new ScionRuntimeException("Error reading file: " + file.toAbsolutePath(), e); + } + return contentBuilder.toString(); + } + + private void parseSciondAddresses(String content) { + JsonElement jsonTree = JsonParser.parseString(content); + JsonObject entry = jsonTree.getAsJsonObject(); + for (Map.Entry e : entry.entrySet()) { + daemons.put(ScionUtil.parseIA(e.getKey()), e.getValue().getAsString()); + } + } + + private void buildSegments() { + List cores = new ArrayList<>(); + topologies.values().stream().filter(LocalTopology::isCoreAs).forEach(cores::add); + for (LocalTopology core : cores) { + for (BorderRouter br : core.getBorderRouters()) { + for (BorderRouterInterface brIf : br.getInterfaces()) { + LocalTopology nextAs = topologies.get(brIf.getIsdAs()); + // Choose some "random" segment ID + int segmentId = 10000 + brIf.getId(); + if (nextAs.isCoreAs()) { + buildSegment(core, brIf, BorderRouterInterface.CORE, segmentId); + } else { + buildSegment(core, brIf, BorderRouterInterface.CHILD, segmentId); + } + } + } + } + } + + private void buildSegment( + LocalTopology parent, BorderRouterInterface parentIf, String linkType, int segmentId) { + long now = Instant.now().getEpochSecond(); + Seg.SegmentInformation info = + Seg.SegmentInformation.newBuilder().setSegmentId(segmentId).setTimestamp(now).build(); + Seg.PathSegment.Builder builder = + Seg.PathSegment.newBuilder().setSegmentInfo(info.toByteString()); + buildChild(builder, linkType, parent.getIsdAs(), parent, 0, parentIf); + } + + private void buildChild( + Seg.PathSegment.Builder builder, + String linkType, + long rootIsdAs, + LocalTopology prevAs, + int prevIngress, + BorderRouterInterface parentIf) { + LocalTopology local = topologies.get(parentIf.getIsdAs()); + + // Build ingoing entry + Seg.HopEntry he0 = buildHopEntry(0, buildHopField(63, prevIngress, parentIf.getId())); + Seg.ASEntry as0 = buildASEntry(prevAs.getIsdAs(), local.getIsdAs(), prevAs.getMtu(), he0); + builder.addAsEntries(as0); + + Set visited = + builder.getAsEntriesList().stream() + .map(Scenario::getBody) + .map(Seg.ASEntrySignedBody::getIsdAs) + .collect(Collectors.toSet()); + + // Find ingress interface + int ingress = -1; + for (BorderRouter br : local.getBorderRouters()) { + for (BorderRouterInterface brIf : br.getInterfaces()) { + if (prevAs.getIsdAs() == brIf.getIsdAs() + && parentIf.getRemoteUnderlay().equals(brIf.getPublicUnderlay())) { + ingress = brIf.getId(); + } + } + } + if (ingress == -1) { + throw new IllegalStateException(); + } + + // Traverse children + for (BorderRouter br : local.getBorderRouters()) { + for (BorderRouterInterface brIf : br.getInterfaces()) { + if (linkType.equals(brIf.getLinkTo()) && !visited.contains(brIf.getIsdAs())) { + Seg.PathSegment.Builder childBuilder = + Seg.PathSegment.newBuilder(builder.build()); // TODO buildPartial() ? + buildChild(childBuilder, linkType, rootIsdAs, local, ingress, brIf); + } + } + } + + // Add ingress interface + Seg.HopEntry he01 = buildHopEntry(parentIf.getMtu(), buildHopField(63, ingress, 0)); + Seg.ASEntry ase01 = buildASEntry(local.getIsdAs(), ZERO, local.getMtu(), he01); + builder.addAsEntries(ase01); + boolean isCore = BorderRouterInterface.CORE == linkType; + segmentDb.add(new SegmentEntry(local.getIsdAs(), rootIsdAs, builder.build(), isCore)); + } + + private static Seg.HopField buildHopField(int expiry, int ingress, int egress) { + ByteString mac = ByteString.copyFrom(new byte[] {1, 2, 3, 4, 5, 6}); + return Seg.HopField.newBuilder() + .setExpTime(expiry) + .setIngress(ingress) + .setEgress(egress) + .setMac(mac) + .build(); + } + + private static Seg.HopEntry buildHopEntry(int mtu, Seg.HopField hf) { + return Seg.HopEntry.newBuilder().setIngressMtu(mtu).setHopField(hf).build(); + } + + private static Seg.ASEntry buildASEntry(long isdAs, long nextIA, int mtu, Seg.HopEntry he) { + Signed.Header header = + Signed.Header.newBuilder() + .setSignatureAlgorithm(Signed.SignatureAlgorithm.SIGNATURE_ALGORITHM_ECDSA_WITH_SHA256) + .setTimestamp(now()) + .build(); + Seg.ASEntrySignedBody body = + Seg.ASEntrySignedBody.newBuilder() + .setIsdAs(isdAs) + .setNextIsdAs(nextIA) + .setMtu(mtu) + .setHopEntry(he) + .build(); + Signed.HeaderAndBodyInternal habi = + Signed.HeaderAndBodyInternal.newBuilder() + .setHeader(header.toByteString()) + .setBody(body.toByteString()) + .build(); + Signed.SignedMessage sm = + Signed.SignedMessage.newBuilder().setHeaderAndBody(habi.toByteString()).build(); + return Seg.ASEntry.newBuilder().setSigned(sm).build(); + } + + private static Seg.PathSegment buildPath(int id, Seg.ASEntry... entries) { + long now = Instant.now().getEpochSecond(); + Seg.SegmentInformation info = + Seg.SegmentInformation.newBuilder().setSegmentId(id).setTimestamp(now).build(); + Seg.PathSegment.Builder builder = + Seg.PathSegment.newBuilder().setSegmentInfo(info.toByteString()); + builder.addAllAsEntries(Arrays.asList(entries)); + return builder.build(); + } + + private static Seg.SegmentsResponse buildResponse( + Seg.SegmentType type, Seg.PathSegment... paths) { + Seg.SegmentsResponse.Builder replyBuilder = Seg.SegmentsResponse.newBuilder(); + Seg.SegmentsResponse.Segments segments = + Seg.SegmentsResponse.Segments.newBuilder().addAllSegments(Arrays.asList(paths)).build(); + replyBuilder.putSegments(type.getNumber(), segments); + return replyBuilder.build(); + } + + private static Timestamp now() { + Instant now = Instant.now(); + // TODO correct? Set nanos? + return Timestamp.newBuilder().setSeconds(now.getEpochSecond()).setNanos(now.getNano()).build(); + } + + private static Seg.ASEntrySignedBody getBody(Seg.ASEntry asEntry) { + if (!asEntry.hasSigned()) { + throw new UnsupportedOperationException("Unsigned entries are not supported"); + } + Signed.SignedMessage sm = asEntry.getSigned(); + try { + Signed.HeaderAndBodyInternal habi = + Signed.HeaderAndBodyInternal.parseFrom(sm.getHeaderAndBody()); + return Seg.ASEntrySignedBody.parseFrom(habi.getBody()); + } catch (InvalidProtocolBufferException e) { + throw new ScionRuntimeException(e); + } + } +}