io.rsocket.metadata.AuthMetadataCodec Maven / Gradle / Ivy
Show all versions of rsocket-core Show documentation
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;
}
}