Skip to content

Commit f7932f3

Browse files
Work towards supporting base64 encoding/decoding in Metapath. Implemented and tested adapter methods. Implemented Metapath encode function.
1 parent b2e2409 commit f7932f3

File tree

5 files changed

+222
-34
lines changed

5 files changed

+222
-34
lines changed

core/src/main/java/gov/nist/secauto/metaschema/core/datatype/adapter/Base64Adapter.java

Lines changed: 79 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,20 @@
1515
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
1616

1717
import java.nio.ByteBuffer;
18+
import java.nio.charset.StandardCharsets;
19+
import java.util.Arrays;
1820
import java.util.Base64;
1921
import java.util.List;
2022

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

2325
/**
24-
* Support for the Metaschema <a href=
25-
* "https://pages.nist.gov/metaschema/specification/datatypes/#base64">base64</a>
26-
* data type.
26+
* Maintains a byte buffer backed representation of a byte stream parsed from a base64 encoded
27+
* string.
28+
* <p>
29+
* Provides support for the Metaschema
30+
* <a href= "https://pages.nist.gov/metaschema/specification/datatypes/#base64">base64</a> data
31+
* type.
2732
*/
2833
public class Base64Adapter
2934
extends AbstractDataTypeAdapter<ByteBuffer, IBase64BinaryItem> {
@@ -67,20 +72,15 @@ public ByteBuffer copy(Object obj) {
6772
return clone;
6873
}
6974

75+
/**
76+
* Get the wrapped value as a base64 encoded string.
77+
*
78+
* @return the base64 encoded value
79+
*/
7080
@Override
7181
public String asString(Object value) {
72-
ByteBuffer buffer = (ByteBuffer) value;
73-
byte[] array;
74-
if (buffer.hasArray()) {
75-
array = buffer.array();
76-
} else {
77-
// Handle direct buffers
78-
array = new byte[buffer.remaining()];
79-
buffer.get(array);
80-
}
81-
82-
Base64.Encoder encoder = Base64.getEncoder();
83-
return ObjectUtils.notNull(encoder.encodeToString(array));
82+
byte[] array = bufferToBytes((ByteBuffer) value, false);
83+
return ObjectUtils.notNull(Base64.getEncoder().encodeToString(array));
8484
}
8585

8686
@Override
@@ -89,4 +89,68 @@ public IBase64BinaryItem newItem(Object value) {
8989
return IBase64BinaryItem.valueOf(item);
9090
}
9191

92+
@NonNull
93+
public static ByteBuffer encode(@NonNull ByteBuffer decodedBuffer) {
94+
return Base64.getEncoder().encode(decodedBuffer);
95+
}
96+
97+
@NonNull
98+
public static ByteBuffer encodeToByteBuffer(@NonNull String decodedString) {
99+
return encodeToByteBuffer(decodedString.getBytes());
100+
}
101+
102+
@NonNull
103+
public static ByteBuffer encodeToByteBuffer(@NonNull byte[] bytes) {
104+
byte[] encodedBytes = Base64.getEncoder().encode(bytes);
105+
return ByteBuffer.wrap(encodedBytes);
106+
}
107+
108+
@NonNull
109+
public static ByteBuffer decode(@NonNull ByteBuffer encodedBuffer) {
110+
return Base64.getDecoder().decode(encodedBuffer);
111+
}
112+
113+
@NonNull
114+
public static String decodeToString(@NonNull ByteBuffer encodedBuffer) {
115+
ByteBuffer decodedBuffer = decode(encodedBuffer);
116+
byte[] decodedBytes = bufferToBytes(decodedBuffer, true);
117+
return new String(decodedBytes, StandardCharsets.UTF_8);
118+
}
119+
120+
@NonNull
121+
public static String decodeToString(@NonNull String encodedString) {
122+
byte[] decodedBytes = Base64.getDecoder().decode(encodedString);
123+
return new String(decodedBytes, StandardCharsets.UTF_8);
124+
}
125+
126+
@NonNull
127+
public ByteBuffer stringToByteBuffer(@NonNull String text) {
128+
return ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
129+
}
130+
131+
@NonNull
132+
public String byteBufferToString(@NonNull ByteBuffer buffer) {
133+
byte[] bytes = bufferToBytes(buffer, false);
134+
return new String(bytes, StandardCharsets.UTF_8);
135+
}
136+
137+
private static byte[] bufferToBytes(@NonNull ByteBuffer buffer, boolean copy) {
138+
byte[] array;
139+
if (buffer.hasArray()) {
140+
array = buffer.array();
141+
if (copy) {
142+
array = Arrays.copyOf(array, array.length);
143+
}
144+
} else {
145+
// Handle direct buffers
146+
array = new byte[buffer.remaining()];
147+
buffer.mark();
148+
try {
149+
buffer.get(array);
150+
} finally {
151+
buffer.reset();
152+
}
153+
}
154+
return array;
155+
}
92156
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* SPDX-FileCopyrightText: none
3+
* SPDX-License-Identifier: CC0-1.0
4+
*/
5+
6+
package gov.nist.secauto.metaschema.core.metapath.function.library;
7+
8+
import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
9+
import gov.nist.secauto.metaschema.core.metapath.MetapathConstants;
10+
import gov.nist.secauto.metaschema.core.metapath.function.FunctionUtils;
11+
import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
12+
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
13+
import gov.nist.secauto.metaschema.core.metapath.item.IItem;
14+
import gov.nist.secauto.metaschema.core.metapath.item.ISequence;
15+
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IBase64BinaryItem;
16+
import gov.nist.secauto.metaschema.core.metapath.item.atomic.IStringItem;
17+
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
18+
19+
import java.util.List;
20+
21+
import edu.umd.cs.findbugs.annotations.NonNull;
22+
23+
/**
24+
* Provides new Metapath functions that evaluate a Metapath recursively over sequences generated by
25+
* evaluating that expression.
26+
*/
27+
public final class MpBase64Encode {
28+
private static final String NAME = "base64-encode-text";
29+
30+
@NonNull
31+
static final IFunction SIGNATURE_ONE_ARG = IFunction.builder()
32+
.name(NAME)
33+
.namespace(MetapathConstants.NS_METAPATH_FUNCTIONS_EXTENDED)
34+
.deterministic()
35+
.contextIndependent()
36+
.focusIndependent()
37+
.argument(IArgument.builder()
38+
.name("text")
39+
.type(IStringItem.type())
40+
.one()
41+
.build())
42+
.returnType(IBase64BinaryItem.type())
43+
.returnOne()
44+
.functionHandler(MpBase64Encode::executeOneArg)
45+
.build();
46+
47+
private MpBase64Encode() {
48+
// disable construction
49+
}
50+
51+
@SuppressWarnings("unused")
52+
@NonNull
53+
private static ISequence<IBase64BinaryItem> executeOneArg(
54+
@NonNull IFunction function,
55+
@NonNull List<ISequence<?>> arguments,
56+
@NonNull DynamicContext dynamicContext,
57+
IItem focus) {
58+
IStringItem text = FunctionUtils.asType(ObjectUtils.requireNonNull(arguments.get(0).getFirstItem(true)));
59+
return ISequence.of(text.encode());
60+
}
61+
}

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItem.java

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

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

8+
import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter;
89
import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
910
import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
1011
import gov.nist.secauto.metaschema.core.metapath.item.atomic.impl.Base64BinaryItemImpl;
@@ -34,9 +35,28 @@ default IAtomicOrUnionType<IBase64BinaryItem> getType() {
3435
return type();
3536
}
3637

38+
static IBase64BinaryItem encode(@NonNull String value) {
39+
// Encode the string to Base64
40+
return valueOf(Base64Adapter.encodeToByteBuffer(value));
41+
}
42+
43+
@NonNull
44+
static IBase64BinaryItem encode(@NonNull byte[] bytes) {
45+
return valueOf(Base64Adapter.encodeToByteBuffer(bytes));
46+
}
47+
48+
@NonNull
49+
static IBase64BinaryItem encode(@NonNull ByteBuffer buffer) {
50+
return valueOf(Base64Adapter.encode(buffer));
51+
}
52+
53+
default IStringItem decodeAsString() {
54+
// Encode the string to Base64
55+
return IStringItem.valueOf(Base64Adapter.decodeToString(asByteBuffer()));
56+
}
57+
3758
/**
38-
* Construct a new base64 encoded byte sequence item using the provided string
39-
* {@code value}.
59+
* Construct a new base64 byte sequence item using the provided base64 encoded string {@code value}.
4060
*
4161
* @param value
4262
* a string representing base64 encoded data
@@ -59,25 +79,27 @@ static IBase64BinaryItem valueOf(@NonNull String value) {
5979
}
6080

6181
/**
62-
* Construct a new URI base64 encoded byte sequence using the provided
63-
* {@link ByteBuffer} {@code value}.
82+
* Construct a new URI base64 encoded byte sequence using the provided {@link ByteBuffer}
83+
* {@code value}.
84+
* <p>
85+
* The provided buffer will be managed by this instance. Make a copy of the buffer to ensure that
86+
* the position, limit, and mark of the original are not affect by this.
6487
*
65-
* @param value
88+
* @param buffer
6689
* a byte buffer
6790
* @return the new item
6891
*/
6992
@NonNull
70-
static IBase64BinaryItem valueOf(@NonNull ByteBuffer value) {
71-
return new Base64BinaryItemImpl(value);
93+
static IBase64BinaryItem valueOf(@NonNull ByteBuffer buffer) {
94+
return new Base64BinaryItemImpl(buffer);
7295
}
7396

7497
/**
7598
* Cast the provided type to this item type.
7699
*
77100
* @param item
78101
* the item to cast
79-
* @return the original item if it is already this type, otherwise a new item
80-
* cast to this type
102+
* @return the original item if it is already this type, otherwise a new item cast to this type
81103
* @throws InvalidValueForCastFunctionException
82104
* if the provided {@code item} cannot be cast to this type
83105
*/
@@ -116,8 +138,8 @@ default int compareTo(IAnyAtomicItem item) {
116138
*
117139
* @param item
118140
* the item to compare with this value
119-
* @return a negative integer, zero, or a positive integer if this value is less
120-
* than, equal to, or greater than the {@code item}.
141+
* @return a negative integer, zero, or a positive integer if this value is less than, equal to, or
142+
* greater than the {@code item}.
121143
*/
122144
default int compareTo(@NonNull IBase64BinaryItem item) {
123145
return asByteBuffer().compareTo(item.asByteBuffer());

core/src/main/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IStringItem.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ default IAtomicOrUnionType<? extends IStringItem> getType() {
3636
* Construct a new item using the provided string {@code value}.
3737
*
3838
* @param value
39-
* a string value that must conform to Metaschema string validation
40-
* rules
39+
* a string value that must conform to Metaschema string validation rules
4140
* @return the new item
4241
* @throws InvalidTypeMetapathException
4342
* if the value fails string validation
@@ -56,13 +55,17 @@ static IStringItem valueOf(@NonNull String value) {
5655
}
5756
}
5857

58+
default IBase64BinaryItem encode() {
59+
// Encode the string to Base64
60+
return IBase64BinaryItem.encode(asString());
61+
}
62+
5963
/**
6064
* Cast the provided type to this item type.
6165
*
6266
* @param item
6367
* the item to cast
64-
* @return the original item if it is already this type, otherwise a new item
65-
* cast to this type
68+
* @return the original item if it is already this type, otherwise a new item cast to this type
6669
* @throws InvalidValueForCastFunctionException
6770
* if the provided {@code item} cannot be cast to this type
6871
*/
@@ -89,13 +92,12 @@ default IStringItem castAsType(IAnyAtomicItem item) {
8992
}
9093

9194
/**
92-
* Compares this value with the argument. Ordering is in lexical dictionary
93-
* order.
95+
* Compares this value with the argument. Ordering is in lexical dictionary order.
9496
*
9597
* @param other
9698
* the item to compare with this value
97-
* @return a negative integer, zero, or a positive integer if this value is less
98-
* than, equal to, or greater than the {@code item}.
99+
* @return a negative integer, zero, or a positive integer if this value is less than, equal to, or
100+
* greater than the {@code item}.
99101
*/
100102
default int compareTo(@NonNull IStringItem other) {
101103
return asString().compareTo(other.asString());

core/src/test/java/gov/nist/secauto/metaschema/core/metapath/item/atomic/IBase64BinaryItemTest.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,20 @@
88
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
99
import static org.junit.jupiter.api.Assertions.assertEquals;
1010

11+
import gov.nist.secauto.metaschema.core.datatype.adapter.Base64Adapter;
12+
import gov.nist.secauto.metaschema.core.datatype.adapter.MetaschemaDataTypeProvider;
1113
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
1214

1315
import org.junit.jupiter.api.Assertions;
1416
import org.junit.jupiter.api.Test;
17+
import org.junit.jupiter.params.ParameterizedTest;
18+
import org.junit.jupiter.params.provider.Arguments;
19+
import org.junit.jupiter.params.provider.MethodSource;
1520

1621
import java.nio.ByteBuffer;
22+
import java.util.stream.Stream;
23+
24+
import edu.umd.cs.findbugs.annotations.NonNull;
1725

1826
class IBase64BinaryItemTest {
1927
private static final long MIN_LONG = -9_223_372_036_854_775_808L;
@@ -46,4 +54,35 @@ void testCastString() {
4654
() -> assertArrayEquals(actual.asByteBuffer().array(), expected.asByteBuffer().array()),
4755
() -> assertEquals(actual.asString(), expected.asString()));
4856
}
57+
58+
private static Stream<Arguments> provideValuesForEncodeDecode() {
59+
return Stream.of(
60+
Arguments.of("The quick brown fox jumps over the lazy dog",
61+
"VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZw=="),
62+
Arguments.of("Mr. Watson, come here, I need you", "TXIuIFdhdHNvbiwgY29tZSBoZXJlLCBJIG5lZWQgeW91"),
63+
Arguments.of(
64+
"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.",
65+
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gRXRpYW0gZXUgbHVjdHVzIGxvcmVtLiBBbGlxdWFtIG1hbGVzdWFkYSBsb3JlbSBuaXNpLCB1dCB0aW5jaWR1bnQgbmVxdWUgZmV1Z2lhdCB2aXRhZS4gRnVzY2UgcHJldGl1bSBudW5jIGFjIHNhcGllbiBmZXVnaWF0IGFjY3Vtc2FuLiBQcm9pbiBlZ2V0IGxpZ3VsYSBub24gdHVycGlzIGxhb3JlZXQgZmVybWVudHVtLiBBbGlxdWFtIG1pIGp1c3RvLCBncmF2aWRhIGlkIHZ1bHB1dGF0ZSBpZCwgdmVuZW5hdGlzIGV1IGZlbGlzLiBWZXN0aWJ1bHVtIGNvbW1vZG8sIG1hZ25hIHF1aXMgc29sbGljaXR1ZGluIGNvbnNlY3RldHVyLCBlcm9zIGVyYXQgZWxlbWVudHVtIGxpYmVybywgbmVjIGV1aXNtb2QgZWxpdCBhcmN1IG5vbiBkaWFtLiBTZWQgaWFjdWxpcyBkdWkgbGFjdXMsIHZpdGFlIHBsYWNlcmF0IHZlbGl0IGlhY3VsaXMgcXVpcy4gU2VkIGluIGxpZ3VsYSBpbiBlcm9zIGx1Y3R1cyBwb3J0dGl0b3IuIE51bGxhbSBpbiBsYW9yZWV0IGxlby4gQ3JhcyBzZWQgbmlzbCBlZ2V0IHR1cnBpcyBzb2xsaWNpdHVkaW4gbW9sZXN0aWUgZXQgZWdldCB0ZWxsdXMuIFZpdmFtdXMgYWxpcXVhbSBvZGlvIGV0IGR1aSBtYXR0aXMsIGluIHJob25jdXMgbWF1cmlzIGhlbmRyZXJpdC4gTmFtIHZpdmVycmEgbWF0dGlzIHJpc3VzIG5vbiB0cmlzdGlxdWUu"));
66+
}
67+
68+
@ParameterizedTest
69+
@MethodSource("provideValuesForEncodeDecode")
70+
void testEncodeDecodeEncode(@NonNull String expectedDecodedString, @NonNull String expectedEncodedString) {
71+
// test encode to buffer
72+
ByteBuffer encodedBuffer = Base64Adapter.encodeToByteBuffer(expectedDecodedString);
73+
encodedBuffer.mark();
74+
String encodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(encodedBuffer);
75+
assertEquals(expectedEncodedString, encodedString);
76+
77+
// test decode from buffer
78+
ByteBuffer decodedBuffer = Base64Adapter.decode(encodedBuffer);
79+
decodedBuffer.mark();
80+
String decodedString = MetaschemaDataTypeProvider.BASE64.byteBufferToString(decodedBuffer);
81+
assertEquals(expectedDecodedString, decodedString);
82+
83+
// test decode from buffer directly to string
84+
encodedBuffer.reset();
85+
decodedString = Base64Adapter.decodeToString(encodedBuffer);
86+
assertEquals(expectedDecodedString, decodedString);
87+
}
4988
}

0 commit comments

Comments
 (0)