xref: /aosp_15_r20/external/aws-crt-java/src/main/java/software/amazon/awssdk/crt/eventstream/Header.java (revision 3c7ae9de214676c52d19f01067dc1a404272dc11)
1 package software.amazon.awssdk.crt.eventstream;
2 
3 import software.amazon.awssdk.crt.*;
4 
5 import java.nio.ByteBuffer;
6 import java.nio.charset.StandardCharsets;
7 import java.util.*;
8 
9 /**
10  * Event-stream header. This object can be represented in many types, so before
11  * using
12  * the getValueAs*() functions, check the value of getHeaderType() and then
13  * decide
14  * which getValueAs*() function to call based on the returned type.
15  */
16 public class Header {
17     private String headerName;
18     private HeaderType headerType;
19     private byte[] headerValue;
20 
Header()21     private Header() {
22     }
23 
24     /**
25      * Create a header with name of boolean value
26      *
27      * @param name  name for the header.
28      * @param value value for the header.
29      * @return new Header instance
30      */
createHeader(final String name, boolean value)31     public static Header createHeader(final String name, boolean value) {
32         Header header = new Header();
33         checkHeaderNameLen(name);
34         header.headerName = name;
35         header.setValue(value);
36         return header;
37     }
38 
39     /**
40      * Create a header with name of byte or int8 value
41      *
42      * @param name  name for the header
43      * @param value value for the header
44      * @return new Header instance
45      */
createHeader(final String name, byte value)46     public static Header createHeader(final String name, byte value) {
47         Header header = new Header();
48         checkHeaderNameLen(name);
49         header.headerName = name;
50         header.setValue(value);
51         return header;
52     }
53 
54     /**
55      * Create a header with name of String value
56      *
57      * @param name  name for the header
58      * @param value value for the header
59      * @return new Header instance
60      */
createHeader(final String name, final String value)61     public static Header createHeader(final String name, final String value) {
62         Header header = new Header();
63         checkHeaderNameLen(name);
64         header.headerName = name;
65         header.setValue(value);
66         return header;
67     }
68 
69     /**
70      * Create a header with name of short or int16 value
71      *
72      * @param name  name for the header
73      * @param value value for the header
74      * @return new Header instance
75      */
createHeader(final String name, short value)76     public static Header createHeader(final String name, short value) {
77         Header header = new Header();
78         checkHeaderNameLen(name);
79         header.headerName = name;
80         header.setValue(value);
81         return header;
82     }
83 
84     /**
85      * Create a header with name of int or int32 value
86      *
87      * @param name  name for the header
88      * @param value value for the header
89      * @return new Header instance
90      */
createHeader(final String name, int value)91     public static Header createHeader(final String name, int value) {
92         Header header = new Header();
93         checkHeaderNameLen(name);
94         header.headerName = name;
95         header.setValue(value);
96         return header;
97     }
98 
99     /**
100      * Create a header with name of long or int64 value
101      *
102      * @param name  name for the header
103      * @param value value for the header
104      * @return new Header instance
105      */
createHeader(final String name, long value)106     public static Header createHeader(final String name, long value) {
107         Header header = new Header();
108         checkHeaderNameLen(name);
109         header.headerName = name;
110         header.setValue(value);
111         return header;
112     }
113 
114     /**
115      * Create a header with name of Date (assumed to be UTC) value
116      *
117      * @param name  name for the header
118      * @param value value for the header
119      * @return new Header instance
120      */
createHeader(final String name, final Date value)121     public static Header createHeader(final String name, final Date value) {
122         Header header = new Header();
123         checkHeaderNameLen(name);
124         header.headerName = name;
125         header.setValue(value);
126         return header;
127     }
128 
129     /**
130      * Create a header with name of byte[] value
131      *
132      * @param name  name for the header
133      * @param value value for the header
134      * @return new Header instance
135      */
createHeader(final String name, final byte[] value)136     public static Header createHeader(final String name, final byte[] value) {
137         Header header = new Header();
138         checkHeaderNameLen(name);
139         header.headerName = name;
140         header.setValue(value);
141         return header;
142     }
143 
144     /**
145      * Create a header with name of UUID value
146      *
147      * @param name  name for the header
148      * @param value value for the header
149      * @return new Header instance
150      */
createHeader(final String name, final UUID value)151     public static Header createHeader(final String name, final UUID value) {
152         Header header = new Header();
153         checkHeaderNameLen(name);
154         header.headerName = name;
155         header.setValue(value);
156         return header;
157     }
158 
159     /**
160      * Marshals buffer into a Header instance
161      *
162      * @param buffer buffer to read the header data from
163      * @return New instance of Header
164      */
fromByteBuffer(final ByteBuffer buffer)165     public static Header fromByteBuffer(final ByteBuffer buffer) {
166         Header header = new Header();
167 
168         int nameLen = buffer.get();
169         byte[] nameBuffer = new byte[nameLen];
170         buffer.get(nameBuffer);
171         header.headerName = new String(nameBuffer, StandardCharsets.UTF_8);
172 
173         int type = buffer.get();
174         HeaderType headerType = HeaderType.getValueFromInt(type);
175         header.headerType = headerType;
176 
177         switch (headerType) {
178             case BooleanFalse:
179             case BooleanTrue:
180                 break;
181             case Byte:
182                 header.headerValue = new byte[1];
183                 buffer.get(header.headerValue);
184                 break;
185             case Int16:
186                 header.headerValue = new byte[2];
187                 buffer.get(header.headerValue);
188                 break;
189             case Int32:
190                 header.headerValue = new byte[4];
191                 buffer.get(header.headerValue);
192                 break;
193             case Int64:
194                 header.headerValue = new byte[8];
195                 buffer.get(header.headerValue);
196                 break;
197             case ByteBuf:
198                 short bufLen = buffer.getShort();
199                 byte[] bufValue = new byte[bufLen];
200                 buffer.get(bufValue);
201                 header.setValue(bufValue);
202                 break;
203             case String:
204                 short strLen = buffer.getShort();
205                 byte[] strValue = new byte[strLen];
206                 buffer.get(strValue);
207                 header.setValue(new String(strValue, StandardCharsets.UTF_8));
208                 break;
209             case TimeStamp:
210                 header.headerValue = new byte[8];
211                 buffer.get(header.headerValue);
212                 break;
213             case UUID:
214                 header.headerValue = new byte[16];
215                 buffer.get(header.headerValue);
216                 break;
217             default:
218                 throw new CrtRuntimeException("Invalid event-stream header buffer.");
219         }
220 
221         return header;
222     }
223 
224     /**
225      * Writes the value of this header into a buffer, using the wire representation
226      * of
227      * the header.
228      *
229      * @param buffer buffer to write this header into
230      */
writeToByteBuffer(ByteBuffer buffer)231     public void writeToByteBuffer(ByteBuffer buffer) {
232         buffer.put((byte) headerName.length());
233         buffer.put(headerName.getBytes(StandardCharsets.UTF_8));
234         buffer.put((byte) headerType.getEnumIntValue());
235 
236         if (headerType != HeaderType.BooleanFalse && headerType != HeaderType.BooleanTrue) {
237             buffer.put(headerValue);
238         }
239     }
240 
241     /**
242      * Gets the name of the header as a (UTF-8) string
243      *
244      * @return utf-8 encoded string for the header name
245      */
getName()246     public String getName() {
247         return this.headerName;
248     }
249 
250     /**
251      * Gets the header type of the value.
252      *
253      * @return HeaderType for this header
254      */
getHeaderType()255     public HeaderType getHeaderType() {
256         return this.headerType;
257     }
258 
259     /**
260      * Gets the value as a boolean. This assumes you've already checked
261      * getHeaderType()
262      * returns BooleanTrue or BooleanFalse
263      *
264      * @return the value as a boolean
265      */
getValueAsBoolean()266     public boolean getValueAsBoolean() {
267         if (!(headerType == HeaderType.BooleanTrue || headerType == HeaderType.BooleanFalse)) {
268             throw new CrtRuntimeException("Invalid Event-stream header type");
269         }
270 
271         return headerType == HeaderType.BooleanTrue;
272     }
273 
setValue(boolean value)274     private void setValue(boolean value) {
275         if (value) {
276             this.headerType = HeaderType.BooleanTrue;
277         } else {
278             this.headerType = HeaderType.BooleanFalse;
279         }
280     }
281 
282     /**
283      * Gets the value as a byte or int8. This assumes you've already checked
284      * getHeaderType()
285      * returns Byte
286      *
287      * @return the value as a byte
288      */
getValueAsByte()289     public byte getValueAsByte() {
290         checkType(HeaderType.Byte);
291         return headerValue[0];
292     }
293 
setValue(byte value)294     private void setValue(byte value) {
295         headerType = HeaderType.Byte;
296         headerValue = new byte[] { value };
297     }
298 
299     /**
300      * Gets the value as a short or int16. This assumes you've already checked
301      * getHeaderType()
302      * returns Int16
303      *
304      * @return the value as a short
305      */
getValueAsShort()306     public short getValueAsShort() {
307         checkType(HeaderType.Int16);
308         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
309         return valueBuffer.getShort();
310     }
311 
setValue(short value)312     private void setValue(short value) {
313         headerType = HeaderType.Int16;
314         ByteBuffer valueBuffer = ByteBuffer.allocate(2);
315         valueBuffer.putShort(value);
316         headerValue = valueBuffer.array();
317     }
318 
319     /**
320      * Gets the value as an int or int32. This assumes you've already checked
321      * getHeaderType()
322      * returns Int32
323      *
324      * @return the value as a int
325      */
getValueAsInt()326     public int getValueAsInt() {
327         checkType(HeaderType.Int32);
328         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
329         return valueBuffer.getInt();
330     }
331 
setValue(int value)332     private void setValue(int value) {
333         headerType = HeaderType.Int32;
334         ByteBuffer valueBuffer = ByteBuffer.allocate(4);
335         valueBuffer.putInt(value);
336         headerValue = valueBuffer.array();
337     }
338 
339     /**
340      * Gets the value as a long or int64. This assumes you've already checked
341      * getHeaderType()
342      * returns Int64
343      *
344      * @return the value as a long
345      */
getValueAsLong()346     public long getValueAsLong() {
347         checkType(HeaderType.Int64);
348         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
349         return valueBuffer.getLong();
350     }
351 
setValue(long value)352     private void setValue(long value) {
353         headerType = HeaderType.Int64;
354         ByteBuffer valueBuffer = ByteBuffer.allocate(8);
355         valueBuffer.putLong(value);
356         headerValue = valueBuffer.array();
357     }
358 
359     /**
360      * Gets the value as a Date. This assumes you've already checked getHeaderType()
361      * returns TimeStamp
362      *
363      * @return the value as a Date
364      */
getValueAsTimestamp()365     public Date getValueAsTimestamp() {
366         checkType(HeaderType.TimeStamp);
367         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
368         return new Date(valueBuffer.getLong());
369     }
370 
setValue(Date value)371     private void setValue(Date value) {
372         headerType = HeaderType.TimeStamp;
373         ByteBuffer valueBuffer = ByteBuffer.allocate(8);
374         valueBuffer.putLong(value.getTime());
375         headerValue = valueBuffer.array();
376     }
377 
378     /**
379      * Gets the value as a byte[]. This assumes you've already checked
380      * getHeaderType()
381      * returns ByteBuf
382      *
383      * @return the value as a byte[]
384      */
getValueAsBytes()385     public byte[] getValueAsBytes() {
386         checkType(HeaderType.ByteBuf);
387         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
388         short len = valueBuffer.getShort();
389         byte[] bufferVal = new byte[len];
390         valueBuffer.get(bufferVal);
391         return bufferVal;
392     }
393 
setValue(final byte[] value)394     private void setValue(final byte[] value) {
395         if (value.length > Short.MAX_VALUE) {
396             throw new CrtRuntimeException("The max length for a ByteBuf Header value is Short.MAX_VALUE");
397         }
398 
399         headerType = HeaderType.ByteBuf;
400         ByteBuffer valueBuffer = ByteBuffer.allocate(headerType.getWireBytesOverhead() + value.length);
401         valueBuffer.putShort((short) value.length);
402         valueBuffer.put(value);
403         headerValue = valueBuffer.array();
404     }
405 
406     /**
407      * Gets the value as a utf-8 encoded string.
408      * This assumes you've already checked getHeaderType()
409      * returns String
410      *
411      * @return the value as a utf-8 encoded string
412      */
getValueAsString()413     public String getValueAsString() {
414         checkType(HeaderType.String);
415         ByteBuffer valueBuffer = ByteBuffer.wrap(headerValue);
416         short len = valueBuffer.getShort();
417         byte[] bufferVal = new byte[len];
418         valueBuffer.get(bufferVal);
419         return new String(bufferVal, StandardCharsets.UTF_8);
420     }
421 
setValue(final String value)422     private void setValue(final String value) {
423         if (value.length() > Short.MAX_VALUE) {
424             throw new CrtRuntimeException("The max length for a String Header value is Short.MAX_VALUE");
425         }
426 
427         headerType = HeaderType.String;
428         ByteBuffer valueBuffer = ByteBuffer.allocate(headerType.getWireBytesOverhead() + value.length());
429         valueBuffer.putShort((short) value.length());
430         valueBuffer.put(value.getBytes(StandardCharsets.UTF_8));
431         headerValue = valueBuffer.array();
432     }
433 
434     /**
435      * Gets the value as a UUID. This assumes you've already checked getHeaderType()
436      * returns UUID
437      *
438      * @return the value as a UUID
439      */
getValueAsUUID()440     public UUID getValueAsUUID() {
441         checkType(HeaderType.UUID);
442 
443         // I straight up stole this from the private constructor for UUID.
444         // A POX on whomever made it private.
445         long msb = 0;
446         long lsb = 0;
447 
448         int i;
449         for (i = 0; i < 8; ++i) {
450             msb = msb << 8 | (long) (headerValue[i] & 255);
451         }
452 
453         for (i = 8; i < 16; ++i) {
454             lsb = lsb << 8 | (long) (headerValue[i] & 255);
455         }
456         return new UUID(msb, lsb);
457     }
458 
setValue(final UUID value)459     private void setValue(final UUID value) {
460         headerType = HeaderType.UUID;
461         ByteBuffer valueBuffer = ByteBuffer.allocate(16);
462         valueBuffer.putLong(value.getMostSignificantBits());
463         valueBuffer.putLong(value.getLeastSignificantBits());
464         headerValue = valueBuffer.array();
465     }
466 
467     /**
468      * @return the total binary wire representation length of this header.
469      */
getTotalByteLength()470     public int getTotalByteLength() {
471         // name len (1 byte) + header name + type (1 byte)
472         int length = 1 + headerName.length() + 1;
473         // optional variable length specifier
474         length += headerValue != null ? headerValue.length : 0;
475         return length;
476     }
477 
478     /**
479      * @hidden Marshals a list of headers into a usable headers block for an
480      *         event-stream message. Used for sending headers across the JNI
481      *         boundary more efficiently
482      * @param headers list of headers to write to the headers block
483      * @return a byte[] that matches the event-stream header-block format.
484      */
marshallHeadersForJNI(List<Header> headers)485     public static byte[] marshallHeadersForJNI(List<Header> headers) {
486         int totalWireLength = 0;
487         for (Header header : headers) {
488             totalWireLength += header.getTotalByteLength();
489         }
490 
491         byte[] marshalledData = new byte[totalWireLength];
492         ByteBuffer marshalledBuf = ByteBuffer.wrap(marshalledData);
493 
494         for (Header header : headers) {
495             header.writeToByteBuffer(marshalledBuf);
496         }
497 
498         return marshalledData;
499     }
500 
checkType(HeaderType headerType)501     private void checkType(HeaderType headerType) {
502         if (this.headerType != headerType) {
503             throw new CrtRuntimeException("Invalid Event-stream header type");
504         }
505     }
506 
checkHeaderNameLen(final String headerName)507     private static void checkHeaderNameLen(final String headerName) {
508         if (headerName.length() >= Byte.MAX_VALUE) {
509             throw new CrtRuntimeException("Header name must be less than 127 bytes.");
510         }
511     }
512 
513     @Override
equals(Object o)514     public boolean equals(Object o) {
515         if (this == o)
516             return true;
517         if (o == null || getClass() != o.getClass())
518             return false;
519         Header header = (Header) o;
520         return headerName.equals(header.headerName) &&
521                 headerType == header.headerType &&
522                 Arrays.equals(headerValue, header.headerValue);
523     }
524 
525     @Override
hashCode()526     public int hashCode() {
527         int result = Objects.hash(headerName, headerType);
528         result = 31 * result + Arrays.hashCode(headerValue);
529         return result;
530     }
531 }
532