/* * Copyright 2019-present HiveMQ GmbH * * 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 com.hivemq.util; import com.google.common.annotations.VisibleForTesting; import com.hivemq.extension.sdk.api.annotations.NotNull; import io.netty.buffer.ByteBuf; import java.util.Locale; import static com.google.common.base.Preconditions.checkNotNull; import static java.nio.charset.StandardCharsets.UTF_8; /** * @author Dominik Obermaier */ public class Strings { private Strings() { //This is a utility class, don't instantiate it! } /** * Returns the value of a prefixed UTF-8 String from a {@link io.netty.buffer.ByteBuf} * according to the MQTT spec. The UTF-8 String is prefixed with a 16-bit value which * indicates the actual length of the String. * <p> * <b>This method will read from the {@link io.netty.buffer.ByteBuf} and will change the reader * index!</b> * * @param buf the {@link io.netty.buffer.ByteBuf} to read from * @return The UTF-8 String or <code>null</code> if there aren't enough bytes to read. This can happen if this method * can not read the prefixed size of the String or if there are less bytes to read available than * indicated by the prefixed 16-bit length. * @throws java.lang.NullPointerException if the passed {@link io.netty.buffer.ByteBuf} is <code>null</code> */ public static String getPrefixedString(final ByteBuf buf) { checkNotNull(buf); if (buf.readableBytes() < 2) { return null; } final int utf8StringLength = buf.readUnsignedShort(); if (buf.readableBytes() < utf8StringLength) { return null; } return getPrefixedString(buf, utf8StringLength); } public static String getPrefixedString(final ByteBuf buf, final int utf8StringLength) { checkNotNull(buf); final String string = buf.toString(buf.readerIndex(), utf8StringLength, UTF_8); //The ByteBuf.toString method, doesn't move the read index, therefor we have to do this manually. buf.skipBytes(utf8StringLength); return string; } public static String getValidatedPrefixedString(@NotNull final ByteBuf buf, final int utf8StringLength, final boolean validateShouldNotCharacters) { checkNotNull(buf); if (buf.readableBytes() < utf8StringLength) { return null; } final byte[] bytes = new byte[utf8StringLength]; buf.getBytes(buf.readerIndex(), bytes); if (Utf8Utils.containsMustNotCharacters(bytes)) { return null; } if (validateShouldNotCharacters && Utf8Utils.hasControlOrNonCharacter(bytes)) { return null; } //The ByteBuf.getBytes method, doesn't move the read index, therefor we have to do this manually. buf.skipBytes(utf8StringLength); return new String(bytes, UTF_8); } /** * Writes a String onto a {@link io.netty.buffer.ByteBuf}. This encodes the * String according to the MQTT spc. The string gets prefixed with a 16-bit value * which indicates the actual length of the string * * @param string the string to encode * @param buffer the byte buffer * @return the encoded string as {@link io.netty.buffer.ByteBuf} */ public static ByteBuf createPrefixedBytesFromString(final String string, final ByteBuf buffer) { checkNotNull(string); checkNotNull(buffer); if (Utf8Utils.stringIsOneByteCharsOnly(string)) { // In case ther is no character in the string that is encoded with more than one byte in UTF-8, // We can write the string character by character without copying it to a temporary byte array. buffer.writeShort(string.length()); for (int i = 0; i < string.length(); i++) { buffer.writeByte(string.charAt(i)); } } else { final byte[] bytes = string.getBytes(UTF_8); buffer.writeShort(bytes.length); buffer.writeBytes(bytes); } return buffer; } /** * <p>This method can be used to convert a long value into a human readable byte format</p> * * <p>1024 bytes = 1.00 KB</p> * <p>1024*1024 bytes = 1.00 MB</p> * <p>1024*1024*1024 bytes = 1.00 GB</p> * <p>1024*1024*1024*1024 bytes = 1.00 TB</p> * * @param bytes the long value to convert * @return the human readable converted String */ @VisibleForTesting public static String convertBytes(final long bytes) { final long kbDivisor = 1024L; final long mbDivisor = kbDivisor * kbDivisor; final long gbDivisor = mbDivisor * kbDivisor; final long tbDivisor = gbDivisor * kbDivisor; if (bytes <= kbDivisor) { return bytes + " B"; } else if (bytes <= mbDivisor) { final double kb = (double) bytes / kbDivisor; return String.format(Locale.US, "%.2f", kb) + " KB"; } else if (bytes <= gbDivisor) { final double mb = (double) bytes / mbDivisor; return String.format(Locale.US, "%.2f", mb) + " MB"; } else if (bytes <= tbDivisor) { final double gb = (double) bytes / gbDivisor; return String.format(Locale.US, "%.2f", gb) + " GB"; } else { final double tb = (double) bytes / tbDivisor; return String.format(Locale.US, "%.2f", tb) + " TB"; } } }