From e4d094a14a5cf855b83a021e09cec39157e870e4 Mon Sep 17 00:00:00 2001 From: James Rosewell Date: Sun, 15 Nov 2026 03:37:38 +0000 Subject: [PATCH] Test that FodId keeps the layout, the payload version and its constructor out of reach The 51Did specification keeps the offsets and lengths out of a package's public surface (did-specification/README.md lines 33 to 35), keeps the payload version off the identifier (package-surface.md lines 55 and 56), and has a package make an instance only by reading a complete Envelope (identifier-layout.md lines 42 and 43). FodId meets all three, and these tests fail if a later change makes a layout constant, a payload version accessor or the constructor reachable. --- .../pipeline/did/FodIdSurfaceTests.java | 131 ++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 pipeline/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdSurfaceTests.java diff --git a/pipeline/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdSurfaceTests.java b/pipeline/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdSurfaceTests.java new file mode 100644 index 000000000..d8865e67a --- /dev/null +++ b/pipeline/pipeline.did/src/test/java/fiftyone/pipeline/did/FodIdSurfaceTests.java @@ -0,0 +1,131 @@ +/* ********************************************************************* + * This Original Work is copyright of 51 Degrees Mobile Experts Limited. + * Copyright 2026 51 Degrees Mobile Experts Limited, Davidson House, + * Forbury Square, Reading, Berkshire, United Kingdom RG1 3EU. + * + * This Original Work is licensed under the European Union Public Licence + * (EUPL) v.1.2 and is subject to its terms as set out below. + * + * If a copy of the EUPL was not distributed with this file, You can obtain + * one at https://opensource.org/licenses/EUPL-1.2. + * + * The 'Compatible Licences' set out in the Appendix to the EUPL (as may be + * amended by the European Commission) shall be deemed incompatible for + * the purposes of the Work and the provisions of the compatibility + * clause in Article 5 of the EUPL shall not apply. + * + * If using the Work as, or as part of, a network application, by + * including the attribution notice(s) required under Article 5 of the EUPL + * in the end user terms of the application under an appropriate heading, + * such notice(s) shall fulfill the requirements of that article. + * ********************************************************************* */ + +package fiftyone.pipeline.did; + +import com.swancommunity.owid.Owid; +import com.swancommunity.owid.Version; +import org.junit.Test; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** + * Checks that {@link FodId} keeps the byte layout out of its public + * surface, has no member for the payload version, and is only ever made by + * reading a complete Envelope, as the 51Did specification asks. + */ +public class FodIdSurfaceTests { + + /** + * True when a caller outside the package can reach the member. + */ + private static boolean isVisible(Member member) { + int modifiers = member.getModifiers(); + return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); + } + + /** + * did-specification/README.md lines 33 to 35, "Application code MUST + * NOT read the layout by hand, and the packages therefore keep the + * offsets and lengths out of their public surface." + */ + @Test + public void layout_OffsetsAndLengths_AreNotPublic() { + List visible = new ArrayList<>(); + for (Field field : FodId.class.getDeclaredFields()) { + if (isVisible(field)) { + visible.add(field.getName()); + } + } + assertEquals( + "FodId fields a caller can reach", + new ArrayList(), + visible); + } + + /** + * did-specification/package-surface.md lines 55 and 56, "The version + * belongs to the refusal and MUST NOT appear as a member of the + * identifier". The only version a caller reads from a FodId is the + * Envelope's own, through getVersion. + */ + @Test + public void payloadVersion_IsNotAMemberOfTheIdentifier() { + List visible = new ArrayList<>(); + for (Method method : FodId.class.getDeclaredMethods()) { + boolean namesVersion = method.getName() + .toLowerCase(Locale.ROOT) + .contains("version"); + if (isVisible(method) && + namesVersion && + method.getReturnType() != Version.class) { + visible.add(method.getName()); + } + } + assertEquals( + "FodId methods that give a version other than the Envelope's", + new ArrayList(), + visible); + } + + /** + * did-specification/identifier-layout.md lines 42 and 43, "a 51Did + * package MUST create instances only by reading bytes that are already + * a complete Envelope." + */ + @Test + public void instances_AreMadeOnlyByReadingAnEnvelope() { + assertTrue( + "FodId is final, so no subclass can make an instance", + Modifier.isFinal(FodId.class.getModifiers())); + for (Constructor constructor : + FodId.class.getDeclaredConstructors()) { + assertTrue( + "constructor " + constructor + " is private", + Modifier.isPrivate(constructor.getModifiers())); + } + List> readable = Arrays.>asList( + String.class, byte[].class, Owid.class); + for (Method method : FodId.class.getDeclaredMethods()) { + if (isVisible(method) && + Modifier.isStatic(method.getModifiers()) && + method.getReturnType() == FodId.class) { + Class[] parameters = method.getParameterTypes(); + assertTrue( + method.getName() + " takes one encoded Envelope", + parameters.length == 1 && + readable.contains(parameters[0])); + } + } + } +}