From 16abb9eea35cf37a602ccaf73b345613d187eb05 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Wed, 18 Dec 2019 22:39:01 +0200 Subject: [PATCH 1/8] fixes tuples bytebufs Signed-off-by: Oleh Dokuka --- .../java/io/rsocket/buffer/Tuple2ByteBuf.java | 24 ++++++++++--------- .../java/io/rsocket/buffer/Tuple3ByteBuf.java | 12 ++-------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java index 66c68009a..ba6620cb0 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple2ByteBuf.java @@ -134,6 +134,12 @@ public ByteBuffer[] _nioBuffers(int index, int length) { @Override public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { + checkDstIndex(index, length, dstIndex, dst.capacity()); + if (length == 0) { + return this; + } + + // FIXME: check twice here long ri = calculateRelativeIndex(index); index = (int) (ri & Integer.MAX_VALUE); switch ((int) ((ri & MASK) >>> 32L)) { @@ -165,20 +171,22 @@ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.length, capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.limit(), capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf); } @Override public ByteBuf getBytes(int index, final OutputStream out, int length) throws IOException { checkIndex(index, length); + if (length == 0) { + return this; + } + long ri = calculateRelativeIndex(index); index = (int) (ri & Integer.MAX_VALUE); switch ((int) ((ri & MASK) >>> 32L)) { @@ -354,18 +362,12 @@ protected void deallocate() { @Override public String toString(Charset charset) { - StringBuilder builder = new StringBuilder(3); + StringBuilder builder = new StringBuilder(capacity); builder.append(one.toString(charset)); builder.append(two.toString(charset)); return builder.toString(); } - @Override - public String toString(int index, int length, Charset charset) { - // TODO - make this smarter - return toString(charset).substring(index, length); - } - @Override public String toString() { return "Tuple2ByteBuf{" diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java index 1a0c1ec31..be593019f 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/Tuple3ByteBuf.java @@ -235,15 +235,13 @@ public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) { @Override public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.length, capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf, dstIndex, length); } @Override public ByteBuf getBytes(int index, ByteBuffer dst) { ByteBuf dstBuf = Unpooled.wrappedBuffer(dst); - int min = Math.min(dst.limit(), capacity); - return getBytes(0, dstBuf, index, min); + return getBytes(index, dstBuf); } @Override @@ -539,12 +537,6 @@ public String toString(Charset charset) { return builder.toString(); } - @Override - public String toString(int index, int length, Charset charset) { - // TODO - make this smarter - return toString(charset).substring(index, length); - } - @Override public String toString() { return "Tuple3ByteBuf{" From c127f4bdc54e1eddcee40fce4381e1a680abebdb Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Wed, 18 Dec 2019 22:40:11 +0200 Subject: [PATCH 2/8] provides first draft implementation of AuthMetadataFlyweight right now it supports encoding only Signed-off-by: Oleh Dokuka --- .../security/AuthMetadataFlyweight.java | 155 ++++++++++ .../metadata/security/WellKnownAuthType.java | 121 ++++++++ .../java/io/rsocket/util/CharByteBufUtil.java | 171 +++++++++++ .../security/AuthMetadataFlyweightTest.java | 274 ++++++++++++++++++ 4 files changed, 721 insertions(+) create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java create mode 100644 rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java create mode 100644 rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java new file mode 100644 index 000000000..4eba6a1a1 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -0,0 +1,155 @@ +package io.rsocket.metadata.security; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufUtil; +import io.rsocket.buffer.TupleByteBuf; +import io.rsocket.util.CharByteBufUtil; + +public class AuthMetadataFlyweight { + + 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 = 1; + static final int AUTH_TYPE_ID_LENGTH = 1; + + private AuthMetadataFlyweight() {} + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is + * empty string if customAuthType is has length greater than 128 bytes + * @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. + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, String customAuthType, ByteBuf metadata) { + + int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType); + if (actualASCIILength != customAuthType.length()) { + metadata.release(); + throw new IllegalArgumentException("custom auth type must be US_ASCII characters only"); + } + if (actualASCIILength < 1 || actualASCIILength > 128) { + metadata.release(); + 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 TupleByteBuf.of(allocator, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using custom authentication type + * + * @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is + * empty string if customAuthType is has length greater than 128 bytes + * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. + * @param authType the custom mime type to encode. + * @param metadata the metadata value to encode. + */ + public static ByteBuf encodeMetadata( + ByteBufAllocator allocator, WellKnownAuthType authType, ByteBuf metadata) { + + if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE + || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) { + metadata.release(); + 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 TupleByteBuf.of(allocator, headerBuffer, metadata); + } + + /** + * Encode a Authentication CompositeMetadata payload using Simple Authentication format + * + * @throws IllegalArgumentException if username length is greater than 128 + * @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 > 128) { + throw new IllegalArgumentException( + "Username should be shorter than or equal to 128 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) + .writeByte(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 AuthMetadataFlyweight.encodeMetadata(allocator, authType, metadata); + } else { + return AuthMetadataFlyweight.encodeMetadata(allocator, wkn, metadata); + } + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java new file mode 100644 index 000000000..2a6c8378e --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java @@ -0,0 +1,121 @@ +/* + * Copyright 2015-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.rsocket.metadata.security; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * Enumeration of Well Known Auth Types, as defined in the eponymous extension. Such auth types are + * used in composite metadata (which can include routing and/or tracing metadata). Per + * specification, identifiers are between 0 and 127 (inclusive). + */ +public enum WellKnownAuthType { + UNPARSEABLE_AUTH_TYPE("UNPARSEABLE_AUTH_TYPE_DO_NOT_USE", (byte) -2), + UNKNOWN_RESERVED_AUTH_TYPE("UNKNOWN_YET_RESERVED_DO_NOT_USE", (byte) -1), + + SIMPLE("simple", (byte) 0x00), + BEARER("bearer", (byte) 0x01); + // ... reserved for future use ... + + static final WellKnownAuthType[] TYPES_BY_AUTH_ID; + static final Map TYPES_BY_AUTH_STRING; + + static { + // precompute an array of all valid auth ids, filling the blanks with the RESERVED enum + TYPES_BY_AUTH_ID = new WellKnownAuthType[128]; // 0-127 inclusive + Arrays.fill(TYPES_BY_AUTH_ID, UNKNOWN_RESERVED_AUTH_TYPE); + // also prepare a Map of the types by auth string + TYPES_BY_AUTH_STRING = new HashMap<>(128); + + for (WellKnownAuthType value : values()) { + if (value.getIdentifier() >= 0) { + TYPES_BY_AUTH_ID[value.getIdentifier()] = value; + TYPES_BY_AUTH_STRING.put(value.getString(), value); + } + } + } + + private final byte identifier; + private final String str; + + WellKnownAuthType(String str, byte identifier) { + this.str = str; + this.identifier = identifier; + } + + /** + * Find the {@link WellKnownAuthType} for the given identifier (as an {@code int}). Valid + * identifiers are defined to be integers between 0 and 127, inclusive. Identifiers outside of + * this range will produce the {@link #UNPARSEABLE_AUTH_TYPE}. Additionally, some identifiers in + * that range are still only reserved and don't have a type associated yet: this method returns + * the {@link #UNKNOWN_RESERVED_AUTH_TYPE} when passing such an identifier, which lets call sites + * potentially detect this and keep the original representation when transmitting the associated + * metadata buffer. + * + * @param id the looked up identifier + * @return the {@link WellKnownAuthType}, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is out + * of the specification's range, or {@link #UNKNOWN_RESERVED_AUTH_TYPE} if the id is one that + * is merely reserved but unknown to this implementation. + */ + public static WellKnownAuthType fromIdentifier(int id) { + if (id < 0x00 || id > 0x7F) { + return UNPARSEABLE_AUTH_TYPE; + } + return TYPES_BY_AUTH_ID[id]; + } + + /** + * Find the {@link WellKnownAuthType} for the given {@link String} representation. If the + * representation is {@code null} or doesn't match a {@link WellKnownAuthType}, the {@link + * #UNPARSEABLE_AUTH_TYPE} is returned. + * + * @param authType the looked up auth type + * @return the matching {@link WellKnownAuthType}, or {@link #UNPARSEABLE_AUTH_TYPE} if none + * matches + */ + public static WellKnownAuthType fromString(String authType) { + if (authType == null) throw new IllegalArgumentException("type must be non-null"); + + // force UNPARSEABLE if by chance UNKNOWN_RESERVED_AUTH_TYPE's text has been used + if (authType.equals(UNKNOWN_RESERVED_AUTH_TYPE.str)) { + return UNPARSEABLE_AUTH_TYPE; + } + + return TYPES_BY_AUTH_STRING.getOrDefault(authType, UNPARSEABLE_AUTH_TYPE); + } + + /** @return the byte identifier of the auth type, guaranteed to be positive or zero. */ + public byte getIdentifier() { + return identifier; + } + + /** + * @return the auth type represented as a {@link String}, which is made of US_ASCII compatible + * characters only + */ + public String getString() { + return str; + } + + /** @see #getString() */ + @Override + public String toString() { + return str; + } +} diff --git a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java new file mode 100644 index 000000000..cf62f054f --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java @@ -0,0 +1,171 @@ +package io.rsocket.util; + +import static io.netty.util.internal.StringUtil.isSurrogate; + +import io.netty.buffer.ByteBuf; +import io.netty.util.internal.MathUtil; + +public class CharByteBufUtil { + + private static final byte WRITE_UTF_UNKNOWN = (byte) '?'; + + private CharByteBufUtil() {} + + /** + * Returns the exact bytes length of UTF8 character sequence. + * + *

This method is producing the exact length according to {@link #writeUtf8(ByteBuf, char[])}. + */ + public static int utf8Bytes(final char[] seq) { + return utf8ByteCount(seq, 0, seq.length); + } + + /** + * This method is producing the exact length according to {@link #writeUtf8(ByteBuf, char[], int, + * int)}. + */ + public static int utf8Bytes(final char[] seq, int start, int end) { + return utf8ByteCount(checkCharSequenceBounds(seq, start, end), start, end); + } + + private static int utf8ByteCount(final char[] seq, int start, int end) { + int i = start; + // ASCII fast path + while (i < end && seq[i] < 0x80) { + ++i; + } + // !ASCII is packed in a separate method to let the ASCII case be smaller + return i < end ? (i - start) + utf8BytesNonAscii(seq, i, end) : i - start; + } + + private static int utf8BytesNonAscii(final char[] seq, final int start, final int end) { + int encodedLength = 0; + for (int i = start; i < end; i++) { + final char c = seq[i]; + // making it 100% branchless isn't rewarding due to the many bit operations necessary! + if (c < 0x800) { + // branchless version of: (c <= 127 ? 0:1) + 1 + encodedLength += ((0x7f - c) >>> 31) + 1; + } else if (isSurrogate(c)) { + if (!Character.isHighSurrogate(c)) { + encodedLength++; + // WRITE_UTF_UNKNOWN + continue; + } + final char c2; + try { + // Surrogate Pair consumes 2 characters. Optimistically try to get the next character to + // avoid + // duplicate bounds checking with charAt. + c2 = seq[++i]; + } catch (IndexOutOfBoundsException ignored) { + encodedLength++; + // WRITE_UTF_UNKNOWN + break; + } + if (!Character.isLowSurrogate(c2)) { + // WRITE_UTF_UNKNOWN + (Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2) + encodedLength += 2; + continue; + } + // See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. + encodedLength += 4; + } else { + encodedLength += 3; + } + } + return encodedLength; + } + + private static char[] checkCharSequenceBounds(char[] seq, int start, int end) { + if (MathUtil.isOutOfBounds(start, end - start, seq.length)) { + throw new IndexOutOfBoundsException( + "expected: 0 <= start(" + + start + + ") <= end (" + + end + + ") <= seq.length(" + + seq.length + + ')'); + } + return seq; + } + + /** + * Encode a {@link char[]} in UTF-8 and write it + * into {@link ByteBuf}. + * + *

This method returns the actual number of bytes written. + */ + public static int writeUtf8(ByteBuf buf, char[] seq) { + return writeUtf8(buf, seq, 0, seq.length); + } + + /** + * Equivalent to {@link #writeUtf8(ByteBuf, char[]) + * writeUtf8(buf, seq.subSequence(start, end), reserveBytes)} but avoids subsequence object + * allocation if possible. + * + * @return actual number of bytes written + */ + public static int writeUtf8(ByteBuf buf, char[] seq, int start, int end) { + return writeUtf8(buf, buf.writerIndex(), checkCharSequenceBounds(seq, start, end), start, end); + } + + // Fast-Path implementation + static int writeUtf8(ByteBuf buffer, int writerIndex, char[] seq, int start, int end) { + int oldWriterIndex = writerIndex; + + // We can use the _set methods as these not need to do any index checks and reference checks. + // This is possible as we called ensureWritable(...) before. + for (int i = start; i < end; i++) { + char c = seq[i]; + if (c < 0x80) { + buffer.setByte(writerIndex++, (byte) c); + } else if (c < 0x800) { + buffer.setByte(writerIndex++, (byte) (0xc0 | (c >> 6))); + buffer.setByte(writerIndex++, (byte) (0x80 | (c & 0x3f))); + } else if (isSurrogate(c)) { + if (!Character.isHighSurrogate(c)) { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + continue; + } + final char c2; + if (seq.length > ++i) { + // Surrogate Pair consumes 2 characters. Optimistically try to get the next character to + // avoid + // duplicate bounds checking with charAt. If an IndexOutOfBoundsException is thrown we + // will + // re-throw a more informative exception describing the problem. + c2 = seq[i]; + } else { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + break; + } + // Extra method to allow inlining the rest of writeUtf8 which is the most likely code path. + writerIndex = writeUtf8Surrogate(buffer, writerIndex, c, c2); + } else { + buffer.setByte(writerIndex++, (byte) (0xe0 | (c >> 12))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((c >> 6) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | (c & 0x3f))); + } + } + buffer.writerIndex(writerIndex); + return writerIndex - oldWriterIndex; + } + + private static int writeUtf8Surrogate(ByteBuf buffer, int writerIndex, char c, char c2) { + if (!Character.isLowSurrogate(c2)) { + buffer.setByte(writerIndex++, WRITE_UTF_UNKNOWN); + buffer.setByte(writerIndex++, Character.isHighSurrogate(c2) ? WRITE_UTF_UNKNOWN : c2); + return writerIndex; + } + int codePoint = Character.toCodePoint(c, c2); + // See http://www.unicode.org/versions/Unicode7.0.0/ch03.pdf#G2630. + buffer.setByte(writerIndex++, (byte) (0xf0 | (codePoint >> 18))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 12) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | ((codePoint >> 6) & 0x3f))); + buffer.setByte(writerIndex++, (byte) (0x80 | (codePoint & 0x3f))); + return writerIndex; + } +} diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java new file mode 100644 index 000000000..e5cb0be4b --- /dev/null +++ b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java @@ -0,0 +1,274 @@ +package io.rsocket.metadata.security; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.Unpooled; +import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.Test; + +class AuthMetadataFlyweightTest { + + public static final int AUTH_TYPE_ID_LENGTH = 1; + public static final int USER_NAME_BYTES_LENGTH = 1; + public static final String TEST_BEARER_TOKEN = + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJpYXQxIjoxNTE2MjM5MDIyLCJpYXQyIjoxNTE2MjM5MDIyLCJpYXQzIjoxNTE2MjM5MDIyLCJpYXQ0IjoxNTE2MjM5MDIyfQ.ljYuH-GNyyhhLcx-rHMchRkGbNsR2_4aSxo8XjrYrSM"; + + @Test + void shouldCorrectlyEncodeData() { + String username = "test"; + String password = "tset1234"; + + int usernameLength = username.length(); + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + } + + @Test + void shouldCorrectlyEncodeData1() { + String username = "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎"; + String password = "tset1234"; + + int usernameLength = username.getBytes(CharsetUtil.UTF_8).length; + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + } + + @Test + void shouldCorrectlyEncodeData2() { + String username = "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎1234567#4? "; + String password = "tset1234"; + + int usernameLength = username.getBytes(CharsetUtil.UTF_8).length; + int passwordLength = password.length(); + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); + + checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + } + + private static void checkSimpleAuthMetadataEncoding( + String username, String password, int usernameLength, int passwordLength, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(AUTH_TYPE_ID_LENGTH + USER_NAME_BYTES_LENGTH + usernameLength + passwordLength); + + Assertions.assertThat(byteBuf.readUnsignedByte() & ~0x80) + .isEqualTo(WellKnownAuthType.SIMPLE.getIdentifier()); + Assertions.assertThat(byteBuf.readUnsignedByte()).isEqualTo((short) usernameLength); + + Assertions.assertThat(byteBuf.readCharSequence(usernameLength, CharsetUtil.UTF_8)) + .isEqualTo(username); + Assertions.assertThat(byteBuf.readCharSequence(passwordLength, CharsetUtil.UTF_8)) + .isEqualTo(password); + + ReferenceCountUtil.release(byteBuf); + } + + @Test + void shouldThrowExceptionIfUsernameLengthExitsAllowedBounds() { + String username = + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𢴈𢵌𢵧𢺳𣲷𤓓𤶸𤷪𥄫𦉘𦟌𦧲𦧺𧨾𨅝𨈇𨋢𨳊𨳍𨳒𩶘"; + String password = "tset1234"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeSimpleMetadata( + ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray())) + .hasMessage( + "Username should be shorter than or equal to 128 bytes length in UTF-8 encoding"); + } + + @Test + void shouldEncodeBearerMetadata() { + String testToken = TEST_BEARER_TOKEN; + + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeBearerMetadata( + ByteBufAllocator.DEFAULT, testToken.toCharArray()); + + checkBearerAuthMetadataEncoding(testToken, byteBuf); + } + + private static void checkBearerAuthMetadataEncoding(String testToken, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(testToken.getBytes(CharsetUtil.UTF_8).length + AUTH_TYPE_ID_LENGTH); + Assertions.assertThat( + byteBuf.readUnsignedByte() & ~AuthMetadataFlyweight.STREAM_METADATA_KNOWN_MASK) + .isEqualTo(WellKnownAuthType.BEARER.getIdentifier()); + Assertions.assertThat(byteBuf.readSlice(byteBuf.capacity() - 1).toString(CharsetUtil.UTF_8)) + .isEqualTo(testToken); + } + + @Test + void shouldEncodeCustomAuth() { + String payloadAsAText = "testsecuritybuffer"; + ByteBuf testSecurityPayload = + Unpooled.wrappedBuffer(payloadAsAText.getBytes(CharsetUtil.UTF_8)); + + String customAuthType = "myownauthtype"; + ByteBuf buffer = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload); + + checkCustomAuthMetadataEncoding(testSecurityPayload, customAuthType, buffer); + } + + private static void checkCustomAuthMetadataEncoding( + ByteBuf testSecurityPayload, String customAuthType, ByteBuf buffer) { + Assertions.assertThat(buffer.capacity()) + .isEqualTo(1 + customAuthType.length() + testSecurityPayload.capacity()); + Assertions.assertThat(buffer.readUnsignedByte()) + .isEqualTo((short) (customAuthType.length() - 1)); + Assertions.assertThat( + buffer.readCharSequence(customAuthType.length(), CharsetUtil.US_ASCII).toString()) + .isEqualTo(customAuthType); + Assertions.assertThat(buffer.readSlice(testSecurityPayload.capacity())) + .isEqualTo(testSecurityPayload); + + ReferenceCountUtil.release(buffer); + } + + @Test + void shouldThrowOnNonASCIIChars() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + String customAuthType = "1234567#4? 𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage("custom auth type must be US_ASCII characters only"); + + Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); + } + + @Test + void shouldThrowOnOutOfAllowedSizeType() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + // 130 chars + String customAuthType = + "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + + Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); + } + + @Test + void shouldThrowOnOutOfAllowedSizeType1() { + ByteBuf testSecurityPayload = ByteBufAllocator.DEFAULT.buffer(); + String customAuthType = ""; + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) + .hasMessage( + "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); + + Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); + } + + @Test + void shouldEncodeUsingWellKnownAuthType() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.SIMPLE, + ByteBufAllocator.DEFAULT.buffer(3, 3).writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldEncodeUsingWellKnownAuthType1() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.SIMPLE, + ByteBufAllocator.DEFAULT.buffer().writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldEncodeUsingWellKnownAuthType2() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.BEARER, + Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + + checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + } + + @Test + void shouldThrowIfWellKnownAuthTypeIsUnsupportedOrUnknown() { + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer().retain(); + Assertions.assertThat(buffer.refCnt()).isEqualTo(2); + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)); + Assertions.assertThat(buffer.refCnt()).isOne(); + + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)); + Assertions.assertThat(buffer.refCnt()).isZero(); + } + + @Test + void shouldCompressMetadata() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, + "simple", + ByteBufAllocator.DEFAULT.buffer().writeByte(1).writeByte('u').writeByte('p')); + + checkSimpleAuthMetadataEncoding("u", "p", 1, 1, byteBuf); + } + + @Test + void shouldCompressMetadata1() { + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, + "bearer", + Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + + checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + } + + @Test + void shouldNotCompressMetadata() { + ByteBuf testMetadataPayload = + Unpooled.wrappedBuffer(TEST_BEARER_TOKEN.getBytes(CharsetUtil.UTF_8)); + String customAuthType = "testauthtype"; + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, customAuthType, testMetadataPayload); + + checkCustomAuthMetadataEncoding(testMetadataPayload, customAuthType, byteBuf); + } +} From df5bb22a9a2d9557ee75de5f719eb91a5cafea74 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 20 Dec 2019 16:11:16 +0200 Subject: [PATCH 3/8] provides full implementation of AuthMetadataFlyweight Signed-off-by: Oleh Dokuka --- .../rsocket/buffer/AbstractTupleByteBuf.java | 2 +- .../security/AuthMetadataFlyweight.java | 189 +++++++++++++++- .../java/io/rsocket/util/CharByteBufUtil.java | 37 +++ .../security/AuthMetadataFlyweightTest.java | 211 +++++++++++++++++- 4 files changed, 430 insertions(+), 9 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java b/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java index fbac4b1a0..a80605877 100644 --- a/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java +++ b/rsocket-core/src/main/java/io/rsocket/buffer/AbstractTupleByteBuf.java @@ -550,7 +550,7 @@ public int writeCharSequence(CharSequence sequence, Charset charset) { @Override public ByteBuffer internalNioBuffer(int index, int length) { - throw new UnsupportedOperationException(); + return nioBuffer(index, length); } @Override diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index 4eba6a1a1..74a8f503f 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -3,6 +3,8 @@ 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.buffer.TupleByteBuf; import io.rsocket.util.CharByteBufUtil; @@ -14,6 +16,8 @@ public class AuthMetadataFlyweight { static final int USERNAME_BYTES_LENGTH = 1; static final int AUTH_TYPE_ID_LENGTH = 1; + static final char[] EMPTY_CHARS_ARRAY = new char[0]; + private AuthMetadataFlyweight() {} /** @@ -80,7 +84,7 @@ public static ByteBuf encodeMetadata( /** * Encode a Authentication CompositeMetadata payload using Simple Authentication format * - * @throws IllegalArgumentException if username length is greater than 128 + * @throws IllegalArgumentException if username length is greater than 256 * @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. @@ -89,9 +93,9 @@ public static ByteBuf encodeSimpleMetadata( ByteBufAllocator allocator, char[] username, char[] password) { int usernameLength = CharByteBufUtil.utf8Bytes(username); - if (usernameLength > 128) { + if (usernameLength > 256) { throw new IllegalArgumentException( - "Username should be shorter than or equal to 128 bytes length in UTF-8 encoding"); + "Username should be shorter than or equal to 256 bytes length in UTF-8 encoding"); } int passwordLength = CharByteBufUtil.utf8Bytes(password); @@ -152,4 +156,183 @@ public static ByteBuf encodeMetadataWithCompression( return AuthMetadataFlyweight.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 ByteBuf + */ + public static WellKnownAuthType decodeWellKnownAuthType(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 decodeCustomAuthType(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 decodePayload(ByteBuf metadata) { + if (metadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return metadata.readSlice(metadata.readableBytes()); + } + + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded 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 sliced {@link ByteBuf} or {@link Unpooled#EMPTY_BUFFER} if username length is zero + */ + public static ByteBuf decodeUsername(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(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 decodePassword(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() == 0) { + return Unpooled.EMPTY_BUFFER; + } + + return simpleAuthMetadata.readSlice(simpleAuthMetadata.readableBytes()); + } + /** + * Read up to 257 {@code bytes} from the given {@link ByteBuf} where the first byte is username + * length and the subsequent number of bytes equal to decoded 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[] decodeUsernameAsCharArray(ByteBuf simpleAuthMetadata) { + short usernameLength = decodeUsernameLength(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[] decodePasswordAsCharArray(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} where the first byte is + * username length and the subsequent number of bytes equal to decoded length + * + * @param bearerAuthMetadata 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[] decodeBearerTokenAsCharArray(ByteBuf bearerAuthMetadata) { + if (bearerAuthMetadata.readableBytes() == 0) { + return EMPTY_CHARS_ARRAY; + } + + return CharByteBufUtil.readUtf8(bearerAuthMetadata, bearerAuthMetadata.readableBytes()); + } + + private static short decodeUsernameLength(ByteBuf simpleAuthMetadata) { + if (simpleAuthMetadata.readableBytes() < 1) { + throw new IllegalStateException( + "Unable to decode custom username. Not enough readable bytes"); + } + + short usernameLength = simpleAuthMetadata.readUnsignedByte(); + + if (simpleAuthMetadata.readableBytes() < usernameLength) { + throw new IllegalArgumentException( + "Unable to decode username. Malformed username length or content"); + } + + return usernameLength; + } } diff --git a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java index cf62f054f..e011d2a6f 100644 --- a/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java +++ b/rsocket-core/src/main/java/io/rsocket/util/CharByteBufUtil.java @@ -3,7 +3,14 @@ import static io.netty.util.internal.StringUtil.isSurrogate; import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; import io.netty.util.internal.MathUtil; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CoderResult; +import java.util.Arrays; public class CharByteBufUtil { @@ -168,4 +175,34 @@ private static int writeUtf8Surrogate(ByteBuf buffer, int writerIndex, char c, c buffer.setByte(writerIndex++, (byte) (0x80 | (codePoint & 0x3f))); return writerIndex; } + + public static char[] readUtf8(ByteBuf byteBuf, int length) { + CharsetDecoder charsetDecoder = CharsetUtil.UTF_8.newDecoder(); + int en = (int) (length * (double) charsetDecoder.maxCharsPerByte()); + char[] ca = new char[en]; + + CharBuffer charBuffer = CharBuffer.wrap(ca); + ByteBuffer byteBuffer = byteBuf.internalNioBuffer(byteBuf.readerIndex(), length); + byteBuffer.mark(); + try { + CoderResult cr = charsetDecoder.decode(byteBuffer, charBuffer, true); + if (!cr.isUnderflow()) cr.throwException(); + cr = charsetDecoder.flush(charBuffer); + if (!cr.isUnderflow()) cr.throwException(); + + byteBuffer.reset(); + byteBuf.skipBytes(length); + + return safeTrim(charBuffer.array(), charBuffer.position()); + } catch (CharacterCodingException x) { + // Substitution is always enabled, + // so this shouldn't happen + throw new IllegalStateException("unable to decode char array from the given buffer", x); + } + } + + private static char[] safeTrim(char[] ca, int len) { + if (len == ca.length) return ca; + else return Arrays.copyOf(ca, len); + } } diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java index e5cb0be4b..6e0fbdf74 100644 --- a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java @@ -27,7 +27,12 @@ void shouldCorrectlyEncodeData() { AuthMetadataFlyweight.encodeSimpleMetadata( ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); - checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); } @Test @@ -42,7 +47,12 @@ void shouldCorrectlyEncodeData1() { AuthMetadataFlyweight.encodeSimpleMetadata( ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); - checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); } @Test @@ -57,7 +67,12 @@ void shouldCorrectlyEncodeData2() { AuthMetadataFlyweight.encodeSimpleMetadata( ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray()); - checkSimpleAuthMetadataEncoding(username, password, usernameLength, passwordLength, byteBuf); + byteBuf.markReaderIndex(); + checkSimpleAuthMetadataEncoding( + username, password, usernameLength, passwordLength, byteBuf.retain()); + byteBuf.resetReaderIndex(); + checkSimpleAuthMetadataEncodingUsingDecoders( + username, password, usernameLength, passwordLength, byteBuf); } private static void checkSimpleAuthMetadataEncoding( @@ -77,10 +92,32 @@ private static void checkSimpleAuthMetadataEncoding( ReferenceCountUtil.release(byteBuf); } + private static void checkSimpleAuthMetadataEncodingUsingDecoders( + String username, String password, int usernameLength, int passwordLength, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(AUTH_TYPE_ID_LENGTH + USER_NAME_BYTES_LENGTH + usernameLength + passwordLength); + + Assertions.assertThat(AuthMetadataFlyweight.decodeWellKnownAuthType(byteBuf)) + .isEqualTo(WellKnownAuthType.SIMPLE); + byteBuf.markReaderIndex(); + Assertions.assertThat(AuthMetadataFlyweight.decodeUsername(byteBuf).toString(CharsetUtil.UTF_8)) + .isEqualTo(username); + Assertions.assertThat(AuthMetadataFlyweight.decodePassword(byteBuf).toString(CharsetUtil.UTF_8)) + .isEqualTo(password); + byteBuf.resetReaderIndex(); + + Assertions.assertThat(new String(AuthMetadataFlyweight.decodeUsernameAsCharArray(byteBuf))) + .isEqualTo(username); + Assertions.assertThat(new String(AuthMetadataFlyweight.decodePasswordAsCharArray(byteBuf))) + .isEqualTo(password); + + ReferenceCountUtil.release(byteBuf); + } + @Test void shouldThrowExceptionIfUsernameLengthExitsAllowedBounds() { String username = - "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𢴈𢵌𢵧𢺳𣲷𤓓𤶸𤷪𥄫𦉘𦟌𦧲𦧺𧨾𨅝𨈇𨋢𨳊𨳍𨳒𩶘"; + "𠜎𠜱𠝹𠱓𠱸𠲖𠳏𠳕𠴕𠵼𠵿𠸎𠸏𠹷𠺝𠺢𠻗𠻹𠻺𠼭𠼮𠽌𠾴𠾼𠿪𡁜𡁯𡁵𡁶𡁻𡃁𡃉𡇙𢃇𢞵𢫕𢭃𢯊𢱑𢱕𢳂𢴈𢵌𢵧𢺳𣲷𤓓𤶸𤷪𥄫𦉘𦟌𦧲𦧺𧨾𨅝𨈇𨋢𨳊𨳍𨳒𩶘𠜎𠜱𠝹"; String password = "tset1234"; Assertions.assertThatThrownBy( @@ -88,7 +125,7 @@ void shouldThrowExceptionIfUsernameLengthExitsAllowedBounds() { AuthMetadataFlyweight.encodeSimpleMetadata( ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray())) .hasMessage( - "Username should be shorter than or equal to 128 bytes length in UTF-8 encoding"); + "Username should be shorter than or equal to 256 bytes length in UTF-8 encoding"); } @Test @@ -99,7 +136,10 @@ void shouldEncodeBearerMetadata() { AuthMetadataFlyweight.encodeBearerMetadata( ByteBufAllocator.DEFAULT, testToken.toCharArray()); + byteBuf.markReaderIndex(); checkBearerAuthMetadataEncoding(testToken, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(testToken, byteBuf); } private static void checkBearerAuthMetadataEncoding(String testToken, ByteBuf byteBuf) { @@ -112,6 +152,22 @@ private static void checkBearerAuthMetadataEncoding(String testToken, ByteBuf by .isEqualTo(testToken); } + private static void checkBearerAuthMetadataEncodingUsingDecoders( + String testToken, ByteBuf byteBuf) { + Assertions.assertThat(byteBuf.capacity()) + .isEqualTo(testToken.getBytes(CharsetUtil.UTF_8).length + AUTH_TYPE_ID_LENGTH); + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(byteBuf)).isTrue(); + Assertions.assertThat(AuthMetadataFlyweight.decodeWellKnownAuthType(byteBuf)) + .isEqualTo(WellKnownAuthType.BEARER); + byteBuf.markReaderIndex(); + Assertions.assertThat(new String(AuthMetadataFlyweight.decodeBearerTokenAsCharArray(byteBuf))) + .isEqualTo(testToken); + byteBuf.resetReaderIndex(); + Assertions.assertThat( + AuthMetadataFlyweight.decodePayload(byteBuf).toString(CharsetUtil.UTF_8).toString()) + .isEqualTo(testToken); + } + @Test void shouldEncodeCustomAuth() { String payloadAsAText = "testsecuritybuffer"; @@ -217,7 +273,10 @@ void shouldEncodeUsingWellKnownAuthType2() { WellKnownAuthType.BEARER, Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + byteBuf.markReaderIndex(); checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(TEST_BEARER_TOKEN, byteBuf); } @Test @@ -257,7 +316,10 @@ void shouldCompressMetadata1() { "bearer", Unpooled.copiedBuffer(TEST_BEARER_TOKEN, CharsetUtil.UTF_8)); + byteBuf.markReaderIndex(); checkBearerAuthMetadataEncoding(TEST_BEARER_TOKEN, byteBuf); + byteBuf.resetReaderIndex(); + checkBearerAuthMetadataEncodingUsingDecoders(TEST_BEARER_TOKEN, byteBuf); } @Test @@ -271,4 +333,143 @@ void shouldNotCompressMetadata() { checkCustomAuthMetadataEncoding(testMetadataPayload, customAuthType, byteBuf); } + + @Test + void shouldConfirmWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple", Unpooled.EMPTY_BUFFER); + + int initialReaderIndex = metadata.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(metadata)).isTrue(); + Assertions.assertThat(metadata.readerIndex()).isEqualTo(initialReaderIndex); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldConfirmGivenMetadataIsNotAWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple/afafgafadf", Unpooled.EMPTY_BUFFER); + + int initialReaderIndex = metadata.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.isWellKnownAuthType(metadata)).isFalse(); + Assertions.assertThat(metadata.readerIndex()).isEqualTo(initialReaderIndex); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldReadSimpleWellKnownAuthType() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "simple", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.SIMPLE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldReadSimpleWellKnownAuthType1() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadataWithCompression( + ByteBufAllocator.DEFAULT, "bearer", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.BEARER; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldReadSimpleWellKnownAuthType2() { + ByteBuf metadata = + ByteBufAllocator.DEFAULT + .buffer() + .writeByte(3 | AuthMetadataFlyweight.STREAM_METADATA_KNOWN_MASK); + WellKnownAuthType expectedType = WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldNotReadSimpleWellKnownAuthTypeIfEncodedLength() { + ByteBuf metadata = ByteBufAllocator.DEFAULT.buffer().writeByte(3); + WellKnownAuthType expectedType = WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldNotReadSimpleWellKnownAuthTypeIfEncodedLength1() { + ByteBuf metadata = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, "testmetadataauthtype", Unpooled.EMPTY_BUFFER); + WellKnownAuthType expectedType = WellKnownAuthType.UNPARSEABLE_AUTH_TYPE; + checkDecodeWellKnowAuthTypeCorrectly(metadata, expectedType); + } + + @Test + void shouldThrowExceptionIsNotEnoughReadableBytes() { + Assertions.assertThatThrownBy( + () -> AuthMetadataFlyweight.decodeWellKnownAuthType(Unpooled.EMPTY_BUFFER)) + .hasMessage("Unable to decode Well Know Auth type. Not enough readable bytes"); + } + + private static void checkDecodeWellKnowAuthTypeCorrectly( + ByteBuf metadata, WellKnownAuthType expectedType) { + int initialReaderIndex = metadata.readerIndex(); + + WellKnownAuthType wellKnownAuthType = AuthMetadataFlyweight.decodeWellKnownAuthType(metadata); + + Assertions.assertThat(wellKnownAuthType).isEqualTo(expectedType); + Assertions.assertThat(metadata.readerIndex()) + .isNotEqualTo(initialReaderIndex) + .isEqualTo(initialReaderIndex + 1); + + ReferenceCountUtil.release(metadata); + } + + @Test + void shouldReadCustomEncodedAuthType() { + String testAuthType = "TestAuthType"; + ByteBuf byteBuf = + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, testAuthType, Unpooled.EMPTY_BUFFER); + checkDecodeCustomAuthTypeCorrectly(testAuthType, byteBuf); + } + + @Test + void shouldThrowExceptionOnEmptyMetadata() { + Assertions.assertThatThrownBy( + () -> AuthMetadataFlyweight.decodeCustomAuthType(Unpooled.EMPTY_BUFFER)) + .hasMessage("Unable to decode custom Auth type. Not enough readable bytes"); + } + + @Test + void shouldThrowExceptionOnMalformedMetadata_wellknowninstead() { + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.decodeCustomAuthType( + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, + WellKnownAuthType.BEARER, + Unpooled.copiedBuffer(new byte[] {'a', 'b'})))) + .hasMessage("Unable to decode custom Auth type. Incorrect auth type length"); + } + + @Test + void shouldThrowExceptionOnMalformedMetadata_length() { + Assertions.assertThatThrownBy( + () -> + AuthMetadataFlyweight.decodeCustomAuthType( + ByteBufAllocator.DEFAULT.buffer().writeByte(127).writeChar('a').writeChar('b'))) + .hasMessage("Unable to decode custom Auth type. Malformed length or auth type string"); + } + + private static void checkDecodeCustomAuthTypeCorrectly(String testAuthType, ByteBuf byteBuf) { + int initialReaderIndex = byteBuf.readerIndex(); + + Assertions.assertThat(AuthMetadataFlyweight.decodeCustomAuthType(byteBuf).toString()) + .isEqualTo(testAuthType); + Assertions.assertThat(byteBuf.readerIndex()) + .isEqualTo(initialReaderIndex + testAuthType.length() + 1); + } } From bab075ce505a1a62b26821de6c2a5a9005997fe1 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Fri, 20 Dec 2019 16:11:37 +0200 Subject: [PATCH 4/8] provides draft of AuthMetadata Signed-off-by: Oleh Dokuka --- .../metadata/security/AuthMetadata.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java new file mode 100644 index 000000000..9481b0bf5 --- /dev/null +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java @@ -0,0 +1,24 @@ +package io.rsocket.metadata.security; + +import io.netty.buffer.ByteBuf; +import io.rsocket.metadata.CompositeMetadata; +import io.rsocket.metadata.WellKnownMimeType; + +public class AuthMetadata implements CompositeMetadata.Entry { + + final CompositeMetadata.Entry source; + + public AuthMetadata(CompositeMetadata.Entry source) { + this.source = source; + } + + @Override + public ByteBuf getContent() { + return source.getContent(); + } + + @Override + public String getMimeType() { + return WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString(); + } +} From dfe5e535c6a1fe6e7d8e22c2a50261fd6c5a62cf Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sat, 11 Jan 2020 18:23:24 +0200 Subject: [PATCH 5/8] fixes allowed username max length Signed-off-by: Oleh Dokuka --- .../io/rsocket/metadata/security/AuthMetadataFlyweight.java | 6 +++--- .../io/rsocket/metadata/security/WellKnownAuthType.java | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index 74a8f503f..06956448a 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -84,7 +84,7 @@ public static ByteBuf encodeMetadata( /** * Encode a Authentication CompositeMetadata payload using Simple Authentication format * - * @throws IllegalArgumentException if username length is greater than 256 + * @throws IllegalArgumentException if username length is greater than 255 * @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. @@ -93,9 +93,9 @@ public static ByteBuf encodeSimpleMetadata( ByteBufAllocator allocator, char[] username, char[] password) { int usernameLength = CharByteBufUtil.utf8Bytes(username); - if (usernameLength > 256) { + if (usernameLength > 255) { throw new IllegalArgumentException( - "Username should be shorter than or equal to 256 bytes length in UTF-8 encoding"); + "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); } int passwordLength = CharByteBufUtil.utf8Bytes(password); diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java index 2a6c8378e..bd4b656b8 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/WellKnownAuthType.java @@ -17,7 +17,7 @@ package io.rsocket.metadata.security; import java.util.Arrays; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.Map; /** @@ -41,7 +41,7 @@ public enum WellKnownAuthType { TYPES_BY_AUTH_ID = new WellKnownAuthType[128]; // 0-127 inclusive Arrays.fill(TYPES_BY_AUTH_ID, UNKNOWN_RESERVED_AUTH_TYPE); // also prepare a Map of the types by auth string - TYPES_BY_AUTH_STRING = new HashMap<>(128); + TYPES_BY_AUTH_STRING = new LinkedHashMap<>(128); for (WellKnownAuthType value : values()) { if (value.getIdentifier() >= 0) { From 72d25643df926fa2415d4396c92019a7cab7cf62 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Sat, 11 Jan 2020 18:32:47 +0200 Subject: [PATCH 6/8] removes eager release Signed-off-by: Oleh Dokuka --- .../metadata/security/AuthMetadata.java | 24 ------------------- .../security/AuthMetadataFlyweight.java | 3 --- .../security/AuthMetadataFlyweightTest.java | 21 +++++++--------- 3 files changed, 8 insertions(+), 40 deletions(-) delete mode 100644 rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java deleted file mode 100644 index 9481b0bf5..000000000 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadata.java +++ /dev/null @@ -1,24 +0,0 @@ -package io.rsocket.metadata.security; - -import io.netty.buffer.ByteBuf; -import io.rsocket.metadata.CompositeMetadata; -import io.rsocket.metadata.WellKnownMimeType; - -public class AuthMetadata implements CompositeMetadata.Entry { - - final CompositeMetadata.Entry source; - - public AuthMetadata(CompositeMetadata.Entry source) { - this.source = source; - } - - @Override - public ByteBuf getContent() { - return source.getContent(); - } - - @Override - public String getMimeType() { - return WellKnownMimeType.MESSAGE_RSOCKET_AUTHENTICATION.getString(); - } -} diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index 06956448a..569289e44 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -34,11 +34,9 @@ public static ByteBuf encodeMetadata( int actualASCIILength = ByteBufUtil.utf8Bytes(customAuthType); if (actualASCIILength != customAuthType.length()) { - metadata.release(); throw new IllegalArgumentException("custom auth type must be US_ASCII characters only"); } if (actualASCIILength < 1 || actualASCIILength > 128) { - metadata.release(); throw new IllegalArgumentException( "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); } @@ -68,7 +66,6 @@ public static ByteBuf encodeMetadata( if (authType == WellKnownAuthType.UNPARSEABLE_AUTH_TYPE || authType == WellKnownAuthType.UNKNOWN_RESERVED_AUTH_TYPE) { - metadata.release(); throw new IllegalArgumentException("only allowed AuthType should be used"); } diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java index 6e0fbdf74..dd3dc9f1a 100644 --- a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java @@ -125,7 +125,7 @@ void shouldThrowExceptionIfUsernameLengthExitsAllowedBounds() { AuthMetadataFlyweight.encodeSimpleMetadata( ByteBufAllocator.DEFAULT, username.toCharArray(), password.toCharArray())) .hasMessage( - "Username should be shorter than or equal to 256 bytes length in UTF-8 encoding"); + "Username should be shorter than or equal to 255 bytes length in UTF-8 encoding"); } @Test @@ -207,8 +207,6 @@ void shouldThrowOnNonASCIIChars() { AuthMetadataFlyweight.encodeMetadata( ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) .hasMessage("custom auth type must be US_ASCII characters only"); - - Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); } @Test @@ -224,8 +222,6 @@ void shouldThrowOnOutOfAllowedSizeType() { ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) .hasMessage( "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); - - Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); } @Test @@ -239,8 +235,6 @@ void shouldThrowOnOutOfAllowedSizeType1() { ByteBufAllocator.DEFAULT, customAuthType, testSecurityPayload)) .hasMessage( "custom auth type must have a strictly positive length that fits on 7 unsigned bits, ie 1-128"); - - Assertions.assertThat(testSecurityPayload.refCnt()).isZero(); } @Test @@ -281,20 +275,21 @@ void shouldEncodeUsingWellKnownAuthType2() { @Test void shouldThrowIfWellKnownAuthTypeIsUnsupportedOrUnknown() { - ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer().retain(); - Assertions.assertThat(buffer.refCnt()).isEqualTo(2); + ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); Assertions.assertThatThrownBy( () -> AuthMetadataFlyweight.encodeMetadata( - ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)); - Assertions.assertThat(buffer.refCnt()).isOne(); + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); Assertions.assertThatThrownBy( () -> AuthMetadataFlyweight.encodeMetadata( - ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)); - Assertions.assertThat(buffer.refCnt()).isZero(); + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); + + buffer.release(); } @Test From 7a73cc57d3fbb869e5e251ecec71afa6d25441b4 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Jan 2020 17:49:21 +0200 Subject: [PATCH 7/8] fixes formating Signed-off-by: Oleh Dokuka --- .../security/AuthMetadataFlyweightTest.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java index dd3dc9f1a..13d910e15 100644 --- a/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java +++ b/rsocket-core/src/test/java/io/rsocket/metadata/security/AuthMetadataFlyweightTest.java @@ -278,16 +278,16 @@ void shouldThrowIfWellKnownAuthTypeIsUnsupportedOrUnknown() { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); Assertions.assertThatThrownBy( - () -> - AuthMetadataFlyweight.encodeMetadata( - ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) - .hasMessage("only allowed AuthType should be used"); + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); Assertions.assertThatThrownBy( - () -> - AuthMetadataFlyweight.encodeMetadata( - ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) - .hasMessage("only allowed AuthType should be used"); + () -> + AuthMetadataFlyweight.encodeMetadata( + ByteBufAllocator.DEFAULT, WellKnownAuthType.UNPARSEABLE_AUTH_TYPE, buffer)) + .hasMessage("only allowed AuthType should be used"); buffer.release(); } From e392ad8e7b3542b1fce38c49ef2d235abb9f98c6 Mon Sep 17 00:00:00 2001 From: Oleh Dokuka Date: Mon, 27 Jan 2020 21:29:43 +0200 Subject: [PATCH 8/8] provides minor fixes Signed-off-by: Oleh Dokuka --- .../security/AuthMetadataFlyweight.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java index 569289e44..f0f5cf54e 100644 --- a/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java +++ b/rsocket-core/src/main/java/io/rsocket/metadata/security/AuthMetadataFlyweight.java @@ -23,11 +23,11 @@ private AuthMetadataFlyweight() {} /** * Encode a Authentication CompositeMetadata payload using custom authentication type * - * @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is - * empty string if customAuthType is has length greater than 128 bytes * @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) { @@ -55,11 +55,12 @@ public static ByteBuf encodeMetadata( /** * Encode a Authentication CompositeMetadata payload using custom authentication type * - * @throws IllegalArgumentException if customAuthType is non US_ASCII string if customAuthType is - * empty string if customAuthType is has length greater than 128 bytes - * @param allocator the {@link ByteBufAllocator} to use to create intermediate buffers as needed. - * @param authType the custom mime type to encode. + * @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) { @@ -81,7 +82,7 @@ public static ByteBuf encodeMetadata( /** * Encode a Authentication CompositeMetadata payload using Simple Authentication format * - * @throws IllegalArgumentException if username length is greater than 255 + * @throws IllegalArgumentException if the username length is greater than 255 * @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. @@ -172,7 +173,7 @@ public static boolean isWellKnownAuthType(ByteBuf metadata) { * @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 ByteBuf + * @throws IllegalStateException if not enough readable bytes in the given {@link ByteBuf} */ public static WellKnownAuthType decodeWellKnownAuthType(ByteBuf metadata) { if (metadata.readableBytes() < 1) {