Split integrations and export into ext module

feat: move integrations from lib to ext
feat: move content export from lib to ext
feat: rename affected packages for separate module distribution
chore: update Gradle module wiring
chore: adjust JPMS descriptors and dependencies
docs: update module structure documentation
This commit is contained in:
2026-04-01 20:43:10 +02:00
parent 354e9dd9bc
commit d1bdf7d9df
30 changed files with 233 additions and 49 deletions

View File

@@ -1,112 +0,0 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.sdk.content.export;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Base64;
import org.junit.jupiter.api.Test;
class Base64StreamTest {
@Test
void testEncodedOutputFrom4KBInput() throws Exception {
System.out.println("testEncodedOutputFrom4KBInput");
byte[] inputBytes = new byte[4096];
new SecureRandom().nextBytes(inputBytes); // random 4KB data
byte[] inputEncoded = Base64.getEncoder().encode(inputBytes);
System.out.println("...encoded length should be: " + inputEncoded.length);
int lineLength = 76;
String[] prefixes = { "echo ", null };
byte[][] lineSeparators = { " >> file.tmp\r\n".getBytes(StandardCharsets.US_ASCII),
"\n".getBytes(StandardCharsets.US_ASCII), null };
for (String prefix : prefixes) {
for (byte[] lineSep : lineSeparators) {
System.out.printf("...***** testing with prefix=%s and lineSep=%s%n",
prefix == null ? "null" : "\"" + prefix + "\"",
lineSep == null ? "null" : "\"" + new String(lineSep, StandardCharsets.US_ASCII) + "\"");
try (InputStream base64Stream = new Base64Stream(new ByteArrayInputStream(inputBytes),
prefix == null ? null : prefix.getBytes(StandardCharsets.US_ASCII), lineLength, lineSep)) {
String encodedOutput = new String(base64Stream.readAllBytes(), StandardCharsets.US_ASCII);
System.out.println(encodedOutput);
String[] lines = encodedOutput.split("\n");
for (int i = 0; i < lines.length; i++) {
int space = lines[i].indexOf(' ');
if (space > 0) {
lines[i] = lines[i].split(" ")[prefix == null ? 0 : 1];
}
}
if (lineSep != null) {
for (String line : lines) {
assertTrue(line.length() <= lineLength, "Line exceeds max length: " + line.length());
}
}
String joined = String.join("", lines);
System.out.println("...lines: " + lines.length);
System.out.println("......encoded length: " + joined.length());
if (lineSep == null && prefix != null) {
continue;
}
byte[] decoded = Base64.getDecoder().decode(joined);
System.out.println("...decoded length: " + decoded.length);
assertArrayEquals(inputBytes, decoded, "Decoded content does not match original input");
}
}
}
System.out.println("...ok");
}
}

View File

@@ -1,74 +0,0 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.sdk.integrations.covert;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
public class TextualCodecTest {
@Test
void testGenerate100CharacterText() {
System.out.println("testGenerate100CharacterText");
TextualCodec.Generator generator = TextualCodec.Generator.EN;
String result = generator.getText(100);
System.out.println("...generated text: " + result);
assertNotNull(result, "Generated text should not be null");
assertEquals(100, result.length(), "Generated text should have 100 characters");
// Ensure all characters are from the expected alphabet
for (char c : result.toCharArray()) {
assertTrue(TextualCodec.Generator.ENGLISH.containsKey(c),
"Generated character '" + c + "' is not part of the English frequency table");
}
// Optional: Analyze distribution for debugging
Map<Character, Integer> histogram = new HashMap<>();
for (char c : result.toCharArray()) {
histogram.merge(c, 1, Integer::sum);
}
System.out.println("...character distribution: " + histogram);
System.out.println("...ok");
}
}

View File

@@ -1,185 +0,0 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.sdk.integrations.covert.jpeg;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.util.Arrays;
import javax.crypto.SecretKey;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import conflux.Ctx;
import conflux.CtxInterface;
import zeroecho.core.CryptoAlgorithms;
import zeroecho.core.alg.aes.AesKeyGenSpec;
import zeroecho.core.alg.aes.AesSpec;
import zeroecho.sdk.builders.alg.AesDataContentBuilder;
import zeroecho.sdk.builders.core.DataContentChainBuilder;
import zeroecho.sdk.builders.core.PlainBytesBuilder;
import zeroecho.sdk.content.api.DataContent;
class JpegExifIntegrationTest {
@TempDir
Path tempDir;
private static byte[] readAll(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
in.transferTo(out);
return out.toByteArray();
}
@Test
void testEncryptEmbedExtractDecrypt() throws Exception {
System.out.println("testEncryptEmbedExtractDecrypt");
String inputText = "Hello, this is a secret message to embed in JPEG.";
inputText = inputText + inputText;
inputText = inputText + inputText;
inputText = inputText + inputText;
final byte[] inputBytes = inputText.getBytes(StandardCharsets.UTF_8);
final byte[] aad = { 1, 2, 3 };
System.out.println("...inputBytes.length=" + inputBytes.length);
// AES encryption setup
/*
* CryptoAlgorithm aes = CryptoAlgorithms.require("AES"); SecretKey key =
* aes.symmetricKeyBuilder(AesKeyGenSpec.class).generateSecret(AesKeyGenSpec.
* aes256()); AesSpec spec =
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
* EncryptionContext enc = CryptoAlgorithms.create("AES", KeyUsage.ENCRYPT, key,
* spec); CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" +
* System.nanoTime()); session.put(ConfluxKeys.aad("AES"), aad); ((ContextAware)
* enc).setContext(session);
*/
SecretKey key = CryptoAlgorithms.require("AES").symmetricKeyBuilder(AesKeyGenSpec.class)
.generateSecret(AesKeyGenSpec.aes256());
CtxInterface session = Ctx.INSTANCE.getContext("aes-ctx-" + System.nanoTime());
byte[] encryptedBytes;
DataContent dccb = DataContentChainBuilder.encrypt()
// input
.add(PlainBytesBuilder.builder().bytes(inputBytes))
// encryption
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded())
// using general AES/GCM/128 without specified header
.spec(AesSpec.gcm128(null))
// but let the builder add the default header for storing AAD and IV
.withHeader().withAad(aad).context(session))
// and create the pipeline
.build();
try (InputStream in = dccb.getStream()) {
encryptedBytes = readAll(in);
}
System.out.println("...encryptedBytes.length=" + encryptedBytes.length);
System.out.println("...-> " + Arrays.toString(encryptedBytes));
// Prepare JPEG test image
Path jpegOriginal = getResourcePath("test.jpg");
Path jpegEmbedded = tempDir.resolve("stego.jpg");
// Embed payload
try (InputStream payloadInput = new ByteArrayInputStream(encryptedBytes);
OutputStream jpegOutput = Files.newOutputStream(jpegEmbedded)) {
JpegExifEmbedder embedder = new JpegExifEmbedder();
embedder.setSlots(Slot.defaults());
int embed_size = embedder.embed(jpegOriginal, payloadInput, jpegOutput);
System.out.println("...embeddedStream.length=" + embed_size);
}
// Extract payload
ByteArrayOutputStream extractedEncrypted = new ByteArrayOutputStream();
JpegExifEmbedder embedder = new JpegExifEmbedder();
embedder.setSlots(Slot.defaults());
embedder.extract(jpegEmbedded, extractedEncrypted);
byte[] extractedEncryptedBytes = extractedEncrypted.toByteArray();
System.out.println("...extractedEncryptedBytes.length=" + extractedEncryptedBytes.length);
System.out.println("...-> " + Arrays.toString(extractedEncryptedBytes));
// Decrypt
dccb = DataContentChainBuilder.decrypt()
// input
.add(PlainBytesBuilder.builder().bytes(extractedEncryptedBytes))
// encryption
.add(AesDataContentBuilder.builder().importKeyRaw(key.getEncoded()).spec(AesSpec.gcm128(null))
// let us use the default header for AAD and IV
.withHeader().withAad(aad).context(session))
// and create the pipeline
.build();
byte[] pt1;
try (InputStream in = dccb.getStream()) {
pt1 = readAll(in);
}
/*
* AesSpec spec =
* AesSpec.builder().mode(Mode.GCM).tagLenBits(128).header(null).build();
* EncryptionContext dec1 = CryptoAlgorithms.create("AES", KeyUsage.DECRYPT,
* key, spec); ((ContextAware) dec1).setContext(session); // same IV/AAD in ctx
* byte[] pt1 = readAll(dec1.attach(new
* ByteArrayInputStream(extractedEncryptedBytes))); dec1.close();
*/
String decrypted = new String(pt1, StandardCharsets.UTF_8);
assertEquals(inputText, decrypted);
System.out.println("...ok");
}
private Path getResourcePath(String resource) throws URISyntaxException {
URL url = getClass().getClassLoader().getResource(resource);
if (url == null) {
throw new IllegalArgumentException("Missing resource: " + resource);
}
return Paths.get(url.toURI());
}
}

View File

@@ -1,88 +0,0 @@
/*******************************************************************************
* Copyright (C) 2026, Leo Galambos
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. All advertising materials mentioning features or use of this software must
* display the following acknowledgement:
* This product includes software developed by the Egothor project.
*
* 4. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package zeroecho.sdk.integrations.stegano;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import org.junit.jupiter.api.Test;
public class LSBSteganographyMethodTest {
@Test
void testEmbedAndExtract() throws Exception {
SteganographyMethod method = new LSBSteganographyMethod();
String message = "Hello from LSB!";
InputStream imageIn = getClass().getResourceAsStream("/test.jpg");
InputStream msgIn = new ByteArrayInputStream(message.getBytes());
// Embed
InputStream embedded = method.embed(imageIn, ImageFormat.PNG, msgIn);
// Extract
InputStream extracted = method.extract(embedded);
String result = new String(extracted.readAllBytes());
assertEquals(message, result);
}
@Test
void testMetadata() {
SteganographyMethod method = new LSBSteganographyMethod();
StegoMetadata meta = method.getMetadata();
assertEquals("LSB", meta.name());
assertTrue(meta.fullName().contains("Least Significant"));
}
@Test
void createSample() throws Exception {
// for verification purposes
SteganographyMethod method = new LSBSteganographyMethod();
String message = "Hello from LSB!";
InputStream imageIn = getClass().getResourceAsStream("/test.jpg");
InputStream msgIn = new ByteArrayInputStream(message.getBytes());
// Embed
InputStream embedded = method.embed(imageIn, ImageFormat.PNG, msgIn);
embedded.transferTo(Files.newOutputStream(Paths.get("/tmp/lsb-test.png"), StandardOpenOption.CREATE));
}
}