All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.rsocket.metadata.AuthMetadataCodec Maven / Gradle / Ivy

The newest version!
package io.rsocket.metadata;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import io.rsocket.util.CharByteBufUtil;

public class AuthMetadataCodec {

  static final int STREAM_METADATA_KNOWN_MASK = 0x80; // 1000 0000
  static final byte STREAM_METADATA_LENGTH_MASK = 0x7F; // 0111 1111

  static final int USERNAME_BYTES_LENGTH = 2;
  static final int AUTH_TYPE_ID_LENGTH = 1;

  static final char[] EMPTY_CHARS_ARRAY = new char[0];

  private AuthMetadataCodec() {}

  /**
   * Encode a Authentication CompositeMetadata payload using custom authentication type
   *
   * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
   * @param customAuthType the custom mime type to encode.
   * @param metadata the metadata value to encode.
   * @throws IllegalArgumentException in case of {@code customAuthType} is non US_ASCII string or
   *     empty string or its length is greater than 128 bytes
   */
  public static ByteBuf encodeMetadata(
      ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) {

    int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType);
    if (actualASCIILength != customAuthType.length()) {
      throw new IllegalArgumentException("custom auth type must be US_ASCII characters only");
    }
    if (actualASCIILength < 1 || actualASCIILength > 128) {
      throw new IllegalArgumentException(
          "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128");
    }

    int capacity = 1 + actualASCIILength;
    ByteBuf headerBuffer = allocator.buffer(capacity, capacity);
    // encoded length is one less than actual length, since 0 is never a valid length, which gives
    // wider representation range
    headerBuffer.writeByte(actualASCIILength - 1);

    ByteBufUtil.reserveAndWriteUtf8(headerBuffer, customAuthType, actualASCIILength);

    return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata);
  }

  /**
   * Encode a Authentication CompositeMetadata payload using custom authentication type
   *
   * @param allocator the {@link ByteBufAllocator} to create intermediate buffers as needed.
   * @param authType the well-known mime type to encode.
   * @param metadata the metadata value to encode.
   * @throws IllegalArgumentException in case of {@code authType} is {@link
   *     WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} or {@link
   *     WellKnownAuthType#UNKNOWN_RESERVED_AUTH_TYPE}
   */
  public static ByteBuf encodeMetadata(
      ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) {

    if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE
        || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) {
      throw new IllegalArgumentException("only allowed AuthType should be used");
    }

    int capacity = AUTH_TYPE_ID_LENGTH;
    ByteBuf headerBuffer =
        allocator
            .buffer(capacity, capacity)
            .writeByte(authType.getIdentifier() | STREAM_METADATA_KNOWN_MASK);

    return allocator.compositeBuffer(2).addComponents(true, headerBuffer, metadata);
  }

  /**
   * Encode a Authentication CompositeMetadata payload using Simple Authentication format
   *
   * @throws IllegalArgumentException if the username length is greater than 65535
   * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
   * @param username the char sequence which represents user name.
   * @param password the char sequence which represents user password.
   */
  public static ByteBuf encodeSimpleMetadata(
      ByteBufAllocator allocator, char[] username, char[] password) {

    int usernameLength = CharByteBufUtil.utf8Bytes(username);
    if (usernameLength > 65535) {
      throw new IllegalArgumentException(
          "Username should be shorter than or equal to 65535 bytes length in UTF-8 encoding");
    }

    int passwordLength = CharByteBufUtil.utf8Bytes(password);
    int capacity = AUTH_TYPE_ID_LENGTH + USERNAME_BYTES_LENGTH + usernameLength + passwordLength;
    final ByteBuf buffer =
        allocator
            .buffer(capacity, capacity)
            .writeByte(WellKnownAuthType.SIMPLE.getIdentifier() | STREAM_METADATA_KNOWN_MASK)
            .writeShort(usernameLength);

    CharByteBufUtil.writeUtf8(buffer, username);
    CharByteBufUtil.writeUtf8(buffer, password);

    return buffer;
  }

  /**
   * Encode a Authentication CompositeMetadata payload using Bearer Authentication format
   *
   * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed.
   * @param token the char sequence which represents BEARER token.
   */
  public static ByteBuf encodeBearerMetadata(ByteBufAllocator allocator, char[] token) {

    int tokenLength = CharByteBufUtil.utf8Bytes(token);
    int capacity = AUTH_TYPE_ID_LENGTH + tokenLength;
    final ByteBuf buffer =
        allocator
            .buffer(capacity, capacity)
            .writeByte(WellKnownAuthType.BEARER.getIdentifier() | STREAM_METADATA_KNOWN_MASK);

    CharByteBufUtil.writeUtf8(buffer, token);

    return buffer;
  }

  /**
   * Encode a new Authentication Metadata payload information, first verifying if the passed {@link
   * String} matches a {@link WellKnownAuthType} (in which case it will be encoded in a compressed
   * fashion using the mime id of that type).
   *
   * 

Prefer using {@link #encodeMetadata(ByteBufAllocator, String, ByteBuf)} if you already know * that the mime type is not a {@link WellKnownAuthType}. * * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. * @param authType the mime type to encode, as a {@link String}. well known mime types are * compressed. * @param metadata the metadata value to encode. * @see #encodeMetadata(ByteBufAllocator, WellKnownAuthType, ByteBuf) * @see #encodeMetadata(ByteBufAllocator, String, ByteBuf) */ public static ByteBuf encodeMetadataWithCompression( ByteBufAllocator allocator, String authType, ByteBuf metadata) { WellKnownAuthType wkn = WellKnownAuthType.fromString(authType); if (wkn == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE) { return AuthMetadataCodec.encodeMetadata(allocator, authType, metadata); } else { return AuthMetadataCodec.encodeMetadata(allocator, wkn, metadata); } } /** * Get the first {@code byte} from a {@link ByteBuf} and check whether it is length or {@link * WellKnownAuthType}. Assuming said buffer properly contains such a {@code byte} * * @param metadata byteBuf used to get information from */ public static boolean isWellKnownAuthType(ByteBuf metadata) { byte lengthOrId = metadata.getByte(0); return (lengthOrId & STREAM_METADATA_LENGTH_MASK) != lengthOrId; } /** * Read first byte from the given {@code metadata} and tries to convert it's value to {@link * WellKnownAuthType}. * * @param metadata given metadata buffer to read from * @return Return on of the know Auth types or {@link WellKnownAuthType#UNPARSEABLE_AUTH_TYPE} if * field's value is length or unknown auth type * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} */ public static WellKnownAuthType readWellKnownAuthType(ByteBuf metadata) { if (metadata.readableBytes() < 1) { throw new IllegalStateException( "Unable to decode Well Know Auth type. Not enough readable bytes"); } byte lengthOrId = metadata.readByte(); int normalizedId = (byte) (lengthOrId & STREAM_METADATA_LENGTH_MASK); if (normalizedId != lengthOrId) { return WellKnownAuthType.fromIdentifier(normalizedId); } return WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; } /** * Read up to 129 bytes from the given metadata in order to get the custom Auth Type * * @param metadata * @return */ public static CharSequence readCustomAuthType(ByteBuf metadata) { if (metadata.readableBytes() < 2) { throw new IllegalStateException( "Unable to decode custom Auth type. Not enough readable bytes"); } byte encodedLength = metadata.readByte(); if (encodedLength < 0) { throw new IllegalStateException( "Unable to decode custom Auth type. Incorrect auth type length"); } // encoded length is realLength - 1 in order to avoid intersection with 0x00 authtype int realLength = encodedLength + 1; if (metadata.readableBytes() < realLength) { throw new IllegalArgumentException( "Unable to decode custom Auth type. Malformed length or auth type string"); } return metadata.readCharSequence(realLength, CharsetUtil.US_ASCII); } /** * Read all remaining {@code bytes} from the given {@link ByteBuf} and return sliced * representation of a payload * * @param metadata metadata to get payload from. Please note, the {@code metadata#readIndex} * should be set to the beginning of the payload bytes * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if no bytes readable in the * given one */ public static ByteBuf readPayload(ByteBuf metadata) { if (metadata.readableBytes() == 0) { return Unpooled.EMPTY_BUFFER; } return metadata.readSlice(metadata.readableBytes()); } /** * Read up to 65537 {@code bytes} from the given {@link ByteBuf} where the first two bytes * represent username length and the subsequent number of bytes equal to read length * * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code * simpleAuthMetadata#readIndex} should be set to the username length position * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero */ public static ByteBuf readUsername(ByteBuf simpleAuthMetadata) { int usernameLength = readUsernameLength(simpleAuthMetadata); if (usernameLength == 0) { return Unpooled.EMPTY_BUFFER; } return simpleAuthMetadata.readSlice(usernameLength); } /** * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's * password * * @param simpleAuthMetadata the given metadata to read password from. Please note, the {@code * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if password length is zero */ public static ByteBuf readPassword(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() == 0) { return Unpooled.EMPTY_BUFFER; } return simpleAuthMetadata.readSlice(simpleAuthMetadata.readableBytes()); } /** * Read up to 65537 {@code bytes} from the given {@link ByteBuf} where the first two bytes * represent username length and the subsequent number of bytes equal to read length * * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code * simpleAuthMetadata#readIndex} should be set to the username length byte * @return {@code char[]} which represents UTF-8 username */ public static char[] readUsernameAsCharArray(ByteBuf simpleAuthMetadata) { int usernameLength = readUsernameLength(simpleAuthMetadata); if (usernameLength == 0) { return EMPTY_CHARS_ARRAY; } return CharByteBufUtil.readUtf8(simpleAuthMetadata, usernameLength); } /** * Read all the remaining {@code byte}s from the given {@link ByteBuf} which represents user's * password * * @param simpleAuthMetadata the given metadata to read username from. Please note, the {@code * simpleAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return {@code char[]} which represents UTF-8 password */ public static char[] readPasswordAsCharArray(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() == 0) { return EMPTY_CHARS_ARRAY; } return CharByteBufUtil.readUtf8(simpleAuthMetadata, simpleAuthMetadata.readableBytes()); } /** * Read all the remaining {@code bytes} from the given {@link ByteBuf} * * @param bearerAuthMetadata the given metadata to read username from. Please note, the {@code * bearerAuthMetadata#readIndex} should be set to the beginning of the password bytes * @return {@code char[]} which represents UTF-8 password */ public static char[] readBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { if (bearerAuthMetadata.readableBytes() == 0) { return EMPTY_CHARS_ARRAY; } return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); } private static int readUsernameLength(ByteBuf simpleAuthMetadata) { if (simpleAuthMetadata.readableBytes() < 2) { throw new IllegalStateException( "Unable to decode custom username. Not enough readable bytes"); } int usernameLength = simpleAuthMetadata.readUnsignedShort(); if (simpleAuthMetadata.readableBytes() < usernameLength) { throw new IllegalArgumentException( "Unable to decode username. Malformed username length or content"); } return usernameLength; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy