Skip to content

Commit

Permalink
Work towards supporting base64 encoding/decoding in Metapath. Impleme…
Browse files Browse the repository at this point in the history
…nted and tested adapter methods. Implemented Metapath encode function.
  • Loading branch information
david-waltermire committed Jan 5, 2025
1 parent b2e2409 commit f7932f3
Show file tree
Hide file tree
Showing 5 changed files with 222 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,20 @@
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Support for the Metaschema <a href=
* "https://pages.nist.gov/metaschema/specification/datatypes/#base64">base64</a>
* data type.
* Maintains a byte buffer backed representation of a byte stream parsed from a base64 encoded
* string.
* <p>
* Provides support for the Metaschema
* <a href= "https://pages.nist.gov/metaschema/specification/datatypes/#base64">base64</a> data
* type.
*/
public class Base64Adapter
extends AbstractDataTypeAdapter<ByteBuffer, IBase64BinaryItem> {
Expand Down Expand Up @@ -67,20 +72,15 @@ public ByteBuffer copy(Object obj) {
return clone;
}

/**
* Get the wrapped value as a base64 encoded string.
*
* @return the base64 encoded value
*/
@Override
public String asString(Object value) {
ByteBuffer buffer = (ByteBuffer) value;
byte[] array;
if (buffer.hasArray()) {
array = buffer.array();
} else {
// Handle direct buffers
array = new byte[buffer.remaining()];
buffer.get(array);
}

Base64.Encoder encoder = Base64.getEncoder();
return ObjectUtils.notNull(encoder.encodeToString(array));
byte[] array = bufferToBytes((ByteBuffer) value, false);
return ObjectUtils.notNull(Base64.getEncoder().encodeToString(array));
}

@Override
Expand All @@ -89,4 +89,68 @@ public IBase64BinaryItem newItem(Object value) {
return IBase64BinaryItem.valueOf(item);
}

@NonNull
public static ByteBuffer encode(@NonNull ByteBuffer decodedBuffer) {
return Base64.getEncoder().encode(decodedBuffer);
}

@NonNull
public static ByteBuffer encodeToByteBuffer(@NonNull String decodedString) {
return encodeToByteBuffer(decodedString.getBytes());
}

@NonNull
public static ByteBuffer encodeToByteBuffer(@NonNull byte[] bytes) {
byte[] encodedBytes = Base64.getEncoder().encode(bytes);
return ByteBuffer.wrap(encodedBytes);
}

@NonNull
public static ByteBuffer decode(@NonNull ByteBuffer encodedBuffer) {
return Base64.getDecoder().decode(encodedBuffer);
}

@NonNull
public static String decodeToString(@NonNull ByteBuffer encodedBuffer) {
ByteBuffer decodedBuffer = decode(encodedBuffer);
byte[] decodedBytes = bufferToBytes(decodedBuffer, true);
return new String(decodedBytes, StandardCharsets.UTF_8);
}

@NonNull
public static String decodeToString(@NonNull String encodedString) {
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
return new String(decodedBytes, StandardCharsets.UTF_8);
}

@NonNull
public ByteBuffer stringToByteBuffer(@NonNull String text) {
return ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
}

@NonNull
public String byteBufferToString(@NonNull ByteBuffer buffer) {
byte[] bytes = bufferToBytes(buffer, false);
return new String(bytes, StandardCharsets.UTF_8);
}

private static byte[] bufferToBytes(@NonNull ByteBuffer buffer, boolean copy) {
byte[] array;
if (buffer.hasArray()) {
array = buffer.array();
if (copy) {
array = Arrays.copyOf(array, array.length);
}
} else {
// Handle direct buffers
array = new byte[buffer.remaining()];
buffer.mark();
try {
buffer.get(array);
} finally {
buffer.reset();
}
}
return array;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.metaschema.core.metapath.function.library;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.List;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
* Provides new Metapath functions that evaluate a Metapath recursively over sequences generated by
* evaluating that expression.
*/
public final class MpBase64Encode {
private static final String NAME = "base64-encode-text";

@NonNull
static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
.name(NAME)
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS_EXTENDED)
.deterministic()
.contextIndependent()
.focusIndependent()
.argument(IArgument.builder()
.name("text")
.type(IStringItem.type())
.one()
.build())
.returnType(IBase64BinaryItem.type())
.returnOne()
.functionHandler(MpBase64Encode::executeOneArg)
.build();

private MpBase64Encode() {
// disable construction
}

@SuppressWarnings("unused")
@NonNull
private static ISequence<IBase64BinaryItem> executeOneArg(
@NonNull IFunction function,
@NonNull List<ISequence<?>> arguments,
@NonNull DynamicContext dynamicContext,
IItem focus) {
IStringItem text = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0).getFirstItem(true)));
return ISequence.of(text.encode());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package gov.nist.secauto.metaschema.core.metapath.item.atomic;

import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter;
import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.Base64BinaryItemImpl;
Expand Down Expand Up @@ -34,9 +35,28 @@ default IAtomicOrUnionType<IBase64BinaryItem> getType() {
return type();
}

static IBase64BinaryItem encode(@NonNull String value) {
// Encode the string to Base64
return valueOf(Base64Adapter.encodeToByteBuffer(value));
}

@NonNull
static IBase64BinaryItem encode(@NonNull byte[] bytes) {
return valueOf(Base64Adapter.encodeToByteBuffer(bytes));
}

@NonNull
static IBase64BinaryItem encode(@NonNull ByteBuffer buffer) {
return valueOf(Base64Adapter.encode(buffer));
}

default IStringItem decodeAsString() {
// Encode the string to Base64
return IStringItem.valueOf(Base64Adapter.decodeToString(asByteBuffer()));
}

/**
* Construct a new base64 encoded byte sequence item using the provided string
* {@code value}.
* Construct a new base64 byte sequence item using the provided base64 encoded string {@code value}.
*
* @param value
* a string representing base64 encoded data
Expand All @@ -59,25 +79,27 @@ static IBase64BinaryItem valueOf(@NonNull String value) {
}

/**
* Construct a new URI base64 encoded byte sequence using the provided
* {@link ByteBuffer} {@code value}.
* Construct a new URI base64 encoded byte sequence using the provided {@link ByteBuffer}
* {@code value}.
* <p>
* The provided buffer will be managed by this instance. Make a copy of the buffer to ensure that
* the position, limit, and mark of the original are not affect by this.
*
* @param value
* @param buffer
* a byte buffer
* @return the new item
*/
@NonNull
static IBase64BinaryItem valueOf(@NonNull ByteBuffer value) {
return new Base64BinaryItemImpl(value);
static IBase64BinaryItem valueOf(@NonNull ByteBuffer buffer) {
return new Base64BinaryItemImpl(buffer);
}

/**
* Cast the provided type to this item type.
*
* @param item
* the item to cast
* @return the original item if it is already this type, otherwise a new item
* cast to this type
* @return the original item if it is already this type, otherwise a new item cast to this type
* @throws InvalidValueForCastFunctionException
* if the provided {@code item} cannot be cast to this type
*/
Expand Down Expand Up @@ -116,8 +138,8 @@ default int compareTo(IAnyAtomicItem item) {
*
* @param item
* the item to compare with this value
* @return a negative integer, zero, or a positive integer if this value is less
* than, equal to, or greater than the {@code item}.
* @return a negative integer, zero, or a positive integer if this value is less than, equal to, or
* greater than the {@code item}.
*/
default int compareTo(@NonNull IBase64BinaryItem item) {
return asByteBuffer().compareTo(item.asByteBuffer());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ default IAtomicOrUnionType<? extends IStringItem> getType() {
* Construct a new item using the provided string {@code value}.
*
* @param value
* a string value that must conform to Metaschema string validation
* rules
* a string value that must conform to Metaschema string validation rules
* @return the new item
* @throws InvalidTypeMetapathException
* if the value fails string validation
Expand All @@ -56,13 +55,17 @@ static IStringItem valueOf(@NonNull String value) {
}
}

default IBase64BinaryItem encode() {
// Encode the string to Base64
return IBase64BinaryItem.encode(asString());
}

/**
* Cast the provided type to this item type.
*
* @param item
* the item to cast
* @return the original item if it is already this type, otherwise a new item
* cast to this type
* @return the original item if it is already this type, otherwise a new item cast to this type
* @throws InvalidValueForCastFunctionException
* if the provided {@code item} cannot be cast to this type
*/
Expand All @@ -89,13 +92,12 @@ default IStringItem castAsType(IAnyAtomicItem item) {
}

/**
* Compares this value with the argument. Ordering is in lexical dictionary
* order.
* Compares this value with the argument. Ordering is in lexical dictionary order.
*
* @param other
* the item to compare with this value
* @return a negative integer, zero, or a positive integer if this value is less
* than, equal to, or greater than the {@code item}.
* @return a negative integer, zero, or a positive integer if this value is less than, equal to, or
* greater than the {@code item}.
*/
default int compareTo(@NonNull IStringItem other) {
return asString().compareTo(other.asString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;

import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter;
import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;

import java.nio.ByteBuffer;
import java.util.stream.Stream;

import edu.umd.cs.findbugs.annotations.NonNull;

class IBase64BinaryItemTest {
private static final long MIN_LONG = -9_223_372_036_854_775_808L;
Expand Down Expand Up @@ -46,4 +54,35 @@ void testCastString() {
() -> assertArrayEquals(actual.asByteBuffer().array(), expected.asByteBuffer().array()),
() -> assertEquals(actual.asString(), expected.asString()));
}

private static Stream<Arguments> provideValuesForEncodeDecode() {
return Stream.of(
Arguments.of("The quick brown fox jumps over the lazy dog",
"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="),
Arguments.of("Mr. Watson, come here, I need you", "TXIuIFdhdHNvbiwgY29tZSBoZXJlLCBJIG5lZWQgeW91"),
Arguments.of(
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam eu luctus lorem. Aliquam malesuada lorem nisi, ut tincidunt neque feugiat vitae. Fusce pretium nunc ac sapien feugiat accumsan. Proin eget ligula non turpis laoreet fermentum. Aliquam mi justo, gravida id vulputate id, venenatis eu felis. Vestibulum commodo, magna quis sollicitudin consectetur, eros erat elementum libero, nec euismod elit arcu non diam. Sed iaculis dui lacus, vitae placerat velit iaculis quis. Sed in ligula in eros luctus porttitor. Nullam in laoreet leo. Cras sed nisl eget turpis sollicitudin molestie et eget tellus. Vivamus aliquam odio et dui mattis, in rhoncus mauris hendrerit. Nam viverra mattis risus non tristique.",
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gRXRpYW0gZXUgbHVjdHVzIGxvcmVtLiBBbGlxdWFtIG1hbGVzdWFkYSBsb3JlbSBuaXNpLCB1dCB0aW5jaWR1bnQgbmVxdWUgZmV1Z2lhdCB2aXRhZS4gRnVzY2UgcHJldGl1bSBudW5jIGFjIHNhcGllbiBmZXVnaWF0IGFjY3Vtc2FuLiBQcm9pbiBlZ2V0IGxpZ3VsYSBub24gdHVycGlzIGxhb3JlZXQgZmVybWVudHVtLiBBbGlxdWFtIG1pIGp1c3RvLCBncmF2aWRhIGlkIHZ1bHB1dGF0ZSBpZCwgdmVuZW5hdGlzIGV1IGZlbGlzLiBWZXN0aWJ1bHVtIGNvbW1vZG8sIG1hZ25hIHF1aXMgc29sbGljaXR1ZGluIGNvbnNlY3RldHVyLCBlcm9zIGVyYXQgZWxlbWVudHVtIGxpYmVybywgbmVjIGV1aXNtb2QgZWxpdCBhcmN1IG5vbiBkaWFtLiBTZWQgaWFjdWxpcyBkdWkgbGFjdXMsIHZpdGFlIHBsYWNlcmF0IHZlbGl0IGlhY3VsaXMgcXVpcy4gU2VkIGluIGxpZ3VsYSBpbiBlcm9zIGx1Y3R1cyBwb3J0dGl0b3IuIE51bGxhbSBpbiBsYW9yZWV0IGxlby4gQ3JhcyBzZWQgbmlzbCBlZ2V0IHR1cnBpcyBzb2xsaWNpdHVkaW4gbW9sZXN0aWUgZXQgZWdldCB0ZWxsdXMuIFZpdmFtdXMgYWxpcXVhbSBvZGlvIGV0IGR1aSBtYXR0aXMsIGluIHJob25jdXMgbWF1cmlzIGhlbmRyZXJpdC4gTmFtIHZpdmVycmEgbWF0dGlzIHJpc3VzIG5vbiB0cmlzdGlxdWUu"));
}

@ParameterizedTest
@MethodSource("provideValuesForEncodeDecode")
void testEncodeDecodeEncode(@NonNull String expectedDecodedString, @NonNull String expectedEncodedString) {
// test encode to buffer
ByteBuffer encodedBuffer = Base64Adapter.encodeToByteBuffer(expectedDecodedString);
encodedBuffer.mark();
String encodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(encodedBuffer);
assertEquals(expectedEncodedString, encodedString);

// test decode from buffer
ByteBuffer decodedBuffer = Base64Adapter.decode(encodedBuffer);
decodedBuffer.mark();
String decodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(decodedBuffer);
assertEquals(expectedDecodedString, decodedString);

// test decode from buffer directly to string
encodedBuffer.reset();
decodedString = Base64Adapter.decodeToString(encodedBuffer);
assertEquals(expectedDecodedString, decodedString);
}
}

0 comments on commit f7932f3

Please sign in to comment.