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