diff --git a/application/build.gradle b/application/build.gradle new file mode 100644 index 00000000..d996cd8e --- /dev/null +++ b/application/build.gradle @@ -0,0 +1,8 @@ +plugins { + id "java-library" +} + +dependencies { + + testImplementation projects.testSupport +} \ No newline at end of file diff --git a/base/build.gradle b/base/build.gradle new file mode 100644 index 00000000..b462ce45 --- /dev/null +++ b/base/build.gradle @@ -0,0 +1,8 @@ +apply plugin: "java-library" +apply from: "$rootDir/gradle/configure-java.gradle" + +dependencies { + api libs.gson + api libs.rlib.collections + testImplementation projects.testSupport +} \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/util/DebugUtils.java b/base/src/main/java/javasabr/mqtt/base/utils/DebugUtils.java similarity index 92% rename from src/main/java/com/ss/mqtt/broker/util/DebugUtils.java rename to base/src/main/java/javasabr/mqtt/base/utils/DebugUtils.java index 70a81255..1cb77151 100644 --- a/src/main/java/com/ss/mqtt/broker/util/DebugUtils.java +++ b/base/src/main/java/javasabr/mqtt/base/utils/DebugUtils.java @@ -1,19 +1,18 @@ -package com.ss.mqtt.broker.util; +package javasabr.mqtt.base.utils; import com.google.gson.ExclusionStrategy; import com.google.gson.FieldAttributes; import com.google.gson.Gson; import com.google.gson.GsonBuilder; +import java.lang.StackWalker.Option; +import java.lang.reflect.Field; import java.util.Collection; +import java.util.Set; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.ReflectionUtils; -import java.lang.StackWalker.Option; -import java.lang.reflect.Field; -import java.util.Set; - public class DebugUtils { public static class PrintOnlyProvidedFields implements ExclusionStrategy { @@ -42,8 +41,8 @@ public boolean shouldSkipClass(Class clazz) { } } - private static final MutableArray ADDITIONAL_EXCLUDE_STRATEGIES = - ArrayFactory.copyOnModifyArray(ExclusionStrategy.class); + private static final MutableArray ADDITIONAL_EXCLUDE_STRATEGIES = ArrayFactory.copyOnModifyArray( + ExclusionStrategy.class); private static class ExclusionStrategyContainer implements ExclusionStrategy { @@ -81,7 +80,9 @@ public static void registerIncludedFields(String... fieldNames) { boolean anyMatch = allFields .stream() - .anyMatch(field -> field.getName().equals(fieldName)); + .anyMatch(field -> field + .getName() + .equals(fieldName)); if (!anyMatch) { throw new RuntimeException("Not found field " + fieldName + " in type " + callerClass); diff --git a/build.gradle b/build.gradle index 65c8897b..a0ef39e8 100644 --- a/build.gradle +++ b/build.gradle @@ -11,107 +11,43 @@ buildscript { } } -rootProject.version = "0.0.1" -group = "com.spaceshift" - -allprojects { - - apply plugin: "java" - apply plugin: "groovy" - apply plugin: "org.springframework.boot" +repositories { + maven { + url "https://gitlab.com/api/v4/projects/37512056/packages/maven" + } +} - //sourceCompatibility = JavaVersion.VERSION_17 - //targetCompatibility = JavaVersion.VERSION_17 +rootProject.version = "0.0.1" +group = "javasabr.mqtt" +subprojects { repositories { mavenCentral() mavenLocal() - } - - dependencies { - - implementation libs.rlib.network - implementation libs.rlib.logger.slf4j - implementation libs.springboot.starter.core - implementation libs.springboot.starter.log4j2 - implementation libs.project.reactor.core - implementation libs.gson - - compileOnly libs.jetbrains.annotations - compileOnly libs.jspecify - compileOnly libs.lombok - annotationProcessor libs.lombok - - // FIXME to remove - testImplementation libs.hivemq.mqtt.client - // FIXME to remove - testImplementation libs.moquette.broker - testImplementation libs.spring.test - testImplementation libs.spock.core - testImplementation libs.spock.spring - testImplementation libs.groovy.all - testImplementation libs.byte.buddy.dep - testImplementation libs.objenesis - testCompileOnly libs.jetbrains.annotations - testCompileOnly libs.jspecify - } - - test { - useJUnitPlatform() - failOnNoDiscoveredTests = false - } - - sourceSets { - main { - java { - srcDirs 'src/main/groovy' - } - } - test { - java { - srcDirs 'src/test/groovy' - } + maven { + url "https://gitlab.com/api/v4/projects/37512056/packages/maven" } } - - tasks.withType(Test).configureEach { - maxParallelForks = 2 - forkEvery = 100 - jvmArgs += "--enable-preview" - } - - tasks.withType(JavaCompile).configureEach { - options.compilerArgs += "--enable-preview" - } - - tasks.withType(GroovyCompile).configureEach { - options.forkOptions.jvmArgs += "--enable-preview" - } - - processResources { - filter(ReplaceTokens, tokens: [ - ]) - } } -task buildSingleArtifact(type: GradleBuild) { - group = 'build' - description = 'Build all modules -> Build result artifact' +tasks.register("buildSingleArtifact", GradleBuild) { + group = "build" + description = "Build all modules -> Build result artifact" tasks = ['build', 'bootJar'] } -task buildSingleArtifactWithoutTests(type: GradleBuild) { - group = 'build' - description = 'Build all modules without tests -> Build result artifact' +tasks.register("buildSingleArtifactWithoutTests", GradleBuild) { + group = "build" + description = "Build all modules without tests -> Build result artifact" tasks = ['classes', 'bootJar'] } -wrapper { - gradleVersion = '6.6-milestone-1' - distributionType = Wrapper.DistributionType.ALL -} - configurations.each { it.exclude group: "org.slf4j", module: "slf4j-log4j12" it.exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" } + +wrapper { + gradleVersion = '9.1.0' + distributionType = Wrapper.DistributionType.ALL +} diff --git a/gradle/configure-java.gradle b/gradle/configure-java.gradle new file mode 100644 index 00000000..bec7b36c --- /dev/null +++ b/gradle/configure-java.gradle @@ -0,0 +1,40 @@ +import org.apache.tools.ant.filters.ReplaceTokens + +dependencies { + compileOnly libs.jspecify + compileOnly libs.lombok + annotationProcessor libs.lombok +} + +test { + useJUnitPlatform() + failOnNoDiscoveredTests = false +} + +sourceSets { + main { + java { + srcDirs 'src/main/groovy' + } + } + test { + java { + srcDirs 'src/test/groovy' + } + } +} + +tasks.withType(Test).configureEach { + maxParallelForks = 2 + forkEvery = 100 + jvmArgs += "--enable-preview" +} + +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += "--enable-preview" +} + + +processResources { + filter(ReplaceTokens, tokens: []) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b363cf14..62c662cf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # https://gitlab.com/JavaSaBr/maven-repo/-/packages -rlib = "10.0.alpha" +rlib = "10.0.alpha1" # https://mvnrepository.com/artifact/org.jetbrains/annotations jetbrains-annotations = "26.0.2" # https://mvnrepository.com/artifact/org.projectlombok/lombok @@ -38,6 +38,7 @@ moquette-broker = "0.17" [libraries] rlib-network = { module = "javasabr.rlib:rlib-network", version.ref = "rlib" } rlib-logger-slf4j = { module = "javasabr.rlib:rlib-logger-slf4j", version.ref = "rlib" } +rlib-collections = { module = "javasabr.rlib:rlib-collections", version.ref = "rlib" } springboot-starter-core = { module = "org.springframework.boot:spring-boot-starter", version.ref = "springboot" } springboot-starter-log4j2 = { module = "org.springframework.boot:spring-boot-starter-log4j2", version.ref = "springboot" } project-reactor-core = { module = "io.projectreactor:reactor-core", version.ref = "project-reactor" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e708b1c0..8bdaf60c 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 709c64f8..a35649f5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index fbd7c515..ef07e016 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,81 +15,115 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +132,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index a9f778a7..5eed7ee8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,8 +13,10 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +27,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +43,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -54,49 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/legacy/build.gradle b/legacy/build.gradle new file mode 100644 index 00000000..eb1f1e3e --- /dev/null +++ b/legacy/build.gradle @@ -0,0 +1,30 @@ +plugins { + id "java" + id "groovy" + id "org.springframework.boot" +} + +apply from: "$rootDir/gradle/configure-java.gradle" + +dependencies { + implementation projects.model + implementation projects.base + + implementation libs.rlib.network + implementation libs.rlib.logger.slf4j + implementation libs.springboot.starter.core + implementation libs.springboot.starter.log4j2 + implementation libs.project.reactor.core + + testImplementation projects.testSupport +} + + +tasks.withType(GroovyCompile).configureEach { + options.forkOptions.jvmArgs += "--enable-preview" +} + +configurations.each { + it.exclude group: "org.slf4j", module: "slf4j-log4j12" + it.exclude group: "org.springframework.boot", module: "spring-boot-starter-logging" +} diff --git a/src/main/java/com/ss/mqtt/broker/MqttBrokerApplication.java b/legacy/src/main/java/javasabr/mqtt/legacy/MqttBrokerApplication.java similarity index 54% rename from src/main/java/com/ss/mqtt/broker/MqttBrokerApplication.java rename to legacy/src/main/java/javasabr/mqtt/legacy/MqttBrokerApplication.java index 1c912a2d..93fb2f6f 100644 --- a/src/main/java/com/ss/mqtt/broker/MqttBrokerApplication.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/MqttBrokerApplication.java @@ -1,9 +1,8 @@ -package com.ss.mqtt.broker; +package javasabr.mqtt.legacy; -import com.ss.mqtt.broker.config.MqttBrokerConfig; -import com.ss.mqtt.broker.config.MqttNetworkConfig; +import javasabr.mqtt.legacy.config.MqttBrokerConfig; +import javasabr.mqtt.legacy.config.MqttNetworkConfig; import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; import org.springframework.boot.SpringApplication; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; @@ -15,8 +14,7 @@ MqttNetworkConfig.class }) public class MqttBrokerApplication { - - public static void main(@NotNull String[] args) { - SpringApplication.run(MqttBrokerApplication.class, args); - } + static void main(String[] args) { + SpringApplication.run(MqttBrokerApplication.class, args); + } } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttBrokerConfig.java b/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttBrokerConfig.java new file mode 100644 index 00000000..1f4f45d4 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttBrokerConfig.java @@ -0,0 +1,138 @@ +package javasabr.mqtt.legacy.config; + +import javasabr.mqtt.legacy.handler.client.DefaultMqttClientReleaseHandler; +import javasabr.mqtt.legacy.handler.client.MqttClientReleaseHandler; +import javasabr.mqtt.legacy.handler.packet.in.ConnectInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.DisconnetInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.PacketInHandler; +import javasabr.mqtt.legacy.handler.packet.in.PublishAckInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.PublishCompleteInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.PublishInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.PublishReceiveInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.PublishReleaseInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.SubscribeInPacketHandler; +import javasabr.mqtt.legacy.handler.packet.in.UnsubscribeInPacketHandler; +import javasabr.mqtt.legacy.handler.publish.in.PublishInHandler; +import javasabr.mqtt.legacy.handler.publish.in.Qos0PublishInHandler; +import javasabr.mqtt.legacy.handler.publish.in.Qos1PublishInHandler; +import javasabr.mqtt.legacy.handler.publish.in.Qos2PublishInHandler; +import javasabr.mqtt.legacy.handler.publish.out.PublishOutHandler; +import javasabr.mqtt.legacy.handler.publish.out.Qos0PublishOutHandler; +import javasabr.mqtt.legacy.handler.publish.out.Qos1PublishOutHandler; +import javasabr.mqtt.legacy.handler.publish.out.Qos2PublishOutHandler; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.legacy.service.AuthenticationService; +import javasabr.mqtt.legacy.service.ClientIdRegistry; +import javasabr.mqtt.legacy.service.CredentialSource; +import javasabr.mqtt.legacy.service.MqttSessionService; +import javasabr.mqtt.legacy.service.PublishingService; +import javasabr.mqtt.legacy.service.SubscriptionService; +import javasabr.mqtt.legacy.service.impl.DefaultPublishingService; +import javasabr.mqtt.legacy.service.impl.FileCredentialsSource; +import javasabr.mqtt.legacy.service.impl.InMemoryClientIdRegistry; +import javasabr.mqtt.legacy.service.impl.InMemoryMqttSessionService; +import javasabr.mqtt.legacy.service.impl.SimpleAuthenticationService; +import javasabr.mqtt.legacy.service.impl.SimpleSubscriptionService; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Log4j2 +@Configuration +@RequiredArgsConstructor +public class MqttBrokerConfig { + + private final Environment env; + + @Bean + ClientIdRegistry clientIdRegistry() { + return new InMemoryClientIdRegistry( + env.getProperty( + "client.id.available.chars", + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"), + env.getProperty("client.id.max.length", int.class, 36)); + } + + @Bean + MqttSessionService mqttSessionService() { + return new InMemoryMqttSessionService(env.getProperty("sessions.clean.thread.interval", int.class, 60000)); + } + + @Bean + CredentialSource credentialSource() { + return new FileCredentialsSource(env.getProperty("credentials.source.file.name", "credentials")); + } + + @Bean + AuthenticationService authenticationService(CredentialSource credentialSource) { + return new SimpleAuthenticationService( + credentialSource, + env.getProperty("authentication.allow.anonymous", boolean.class, false)); + } + + @Bean + PacketInHandler[] packetHandlers( + AuthenticationService authenticationService, + ClientIdRegistry clientIdRegistry, + SubscriptionService subscriptionService, + PublishingService publishingService, + MqttSessionService mqttSessionService) { + + var handlers = new PacketInHandler[PacketType.INVALID.ordinal()]; + handlers[PacketType.CONNECT.ordinal()] = new ConnectInPacketHandler( + clientIdRegistry, + authenticationService, + mqttSessionService, + subscriptionService); + handlers[PacketType.SUBSCRIBE.ordinal()] = new SubscribeInPacketHandler(subscriptionService); + handlers[PacketType.UNSUBSCRIBE.ordinal()] = new UnsubscribeInPacketHandler(subscriptionService); + handlers[PacketType.PUBLISH.ordinal()] = new PublishInPacketHandler(publishingService); + handlers[PacketType.DISCONNECT.ordinal()] = new DisconnetInPacketHandler(); + handlers[PacketType.PUBLISH_ACK.ordinal()] = new PublishAckInPacketHandler(); + handlers[PacketType.PUBLISH_RECEIVED.ordinal()] = new PublishReceiveInPacketHandler(); + handlers[PacketType.PUBLISH_RELEASED.ordinal()] = new PublishReleaseInPacketHandler(); + handlers[PacketType.PUBLISH_COMPLETED.ordinal()] = new PublishCompleteInPacketHandler(); + + return handlers; + } + + @Bean + MqttClientReleaseHandler mqttClientReleaseHandler( + ClientIdRegistry clientIdRegistry, + MqttSessionService mqttSessionService, + SubscriptionService subscriptionService) { + return new DefaultMqttClientReleaseHandler(clientIdRegistry, mqttSessionService, subscriptionService); + } + + @Bean + SubscriptionService subscriptionService() { + return new SimpleSubscriptionService(); + } + + @Bean + PublishOutHandler[] publishOutHandlers() { + return new PublishOutHandler[]{ + new Qos0PublishOutHandler(), + new Qos1PublishOutHandler(), + new Qos2PublishOutHandler() + }; + } + + @Bean + PublishInHandler[] publishInHandlers( + SubscriptionService subscriptionService, + PublishOutHandler[] publishOutHandlers) { + return new PublishInHandler[]{ + new Qos0PublishInHandler(subscriptionService, publishOutHandlers), + new Qos1PublishInHandler(subscriptionService, publishOutHandlers), + new Qos2PublishInHandler(subscriptionService, publishOutHandlers) + }; + } + + @Bean + PublishingService publishingService(PublishInHandler[] publishInHandlers) { + return new DefaultPublishingService(publishInHandlers); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttNetworkConfig.java b/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttNetworkConfig.java new file mode 100644 index 00000000..d3202208 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/config/MqttNetworkConfig.java @@ -0,0 +1,294 @@ +package javasabr.mqtt.legacy.config; + +import javasabr.mqtt.legacy.handler.client.MqttClientReleaseHandler; +import javasabr.mqtt.legacy.handler.packet.in.PacketInHandler; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.client.ExternalMqttClient; +import javasabr.mqtt.legacy.network.client.InternalMqttClient; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import java.net.InetSocketAddress; +import java.nio.channels.AsynchronousSocketChannel; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import javasabr.mqtt.model.MqttConnectionConfig; +import javasabr.rlib.network.BufferAllocator; +import javasabr.rlib.network.Network; +import javasabr.rlib.network.NetworkFactory; +import javasabr.rlib.network.ServerNetworkConfig; +import javasabr.rlib.network.ServerNetworkConfig.SimpleServerNetworkConfig; +import javasabr.rlib.network.impl.DefaultBufferAllocator; +import javasabr.rlib.network.server.ServerNetwork; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.Environment; + +@Log4j2 +@Configuration +@RequiredArgsConstructor +public class MqttNetworkConfig { + + private interface ChannelFactory extends + BiFunction, AsynchronousSocketChannel, MqttConnection> {} + + private final Environment env; + + @Bean + ServerNetworkConfig internalNetworkConfig() { + return SimpleServerNetworkConfig + .builder() + .readBufferSize(env.getProperty("mqtt.internal.network.read.buffer.size", int.class, 2048)) + .pendingBufferSize(env.getProperty("mqtt.internal.network.pending.buffer.size", int.class, 4096)) + .writeBufferSize(env.getProperty("mqtt.internal.network.write.buffer.size", int.class, 2048)) + .threadGroupName("InternalNetwork") + .threadGroupSize(env.getProperty("mqtt.internal.network.thread.count", int.class, 1)) + .build(); + } + + @Bean + ServerNetworkConfig externalNetworkConfig() { + return SimpleServerNetworkConfig + .builder() + .readBufferSize(env.getProperty("mqtt.external.network.read.buffer.size", int.class, 100)) + .pendingBufferSize(env.getProperty("mqtt.external.network.pending.buffer.size", int.class, 200)) + .writeBufferSize(env.getProperty("mqtt.external.network.write.buffer.size", int.class, 100)) + .threadGroupName("ExternalNetwork") + .threadGroupSize(env.getProperty("mqtt.external.network.thread.count", int.class, 1)) + .build(); + } + + @Bean + BufferAllocator internalBufferAllocator(ServerNetworkConfig internalNetworkConfig) { + return new DefaultBufferAllocator(internalNetworkConfig); + } + + @Bean + BufferAllocator externalBufferAllocator(ServerNetworkConfig externalNetworkConfig) { + return new DefaultBufferAllocator(externalNetworkConfig); + } + + @Bean + ServerNetwork externalNetwork( + ServerNetworkConfig externalNetworkConfig, + BufferAllocator externalBufferAllocator, + MqttConnectionConfig externalConnectionConfig, + PacketInHandler[] packetHandlers, + MqttClientReleaseHandler mqttClientReleaseHandler) { + return NetworkFactory.newServerNetwork( + externalNetworkConfig, + externalConnectionFactory( + externalBufferAllocator, + externalConnectionConfig, + packetHandlers, + mqttClientReleaseHandler)); + } + + @Bean + ServerNetwork internalNetwork( + ServerNetworkConfig internalNetworkConfig, + BufferAllocator internalBufferAllocator, + MqttConnectionConfig internalConnectionConfig, + PacketInHandler[] packetHandlers, + MqttClientReleaseHandler mqttClientReleaseHandler) { + return NetworkFactory.newServerNetwork( + internalNetworkConfig, + internalConnectionFactory( + internalBufferAllocator, + internalConnectionConfig, + packetHandlers, + mqttClientReleaseHandler)); + } + + @Bean + InetSocketAddress externalNetworkAddress( + ServerNetwork externalNetwork, + Consumer externalConnectionConsumer) { + + var address = new InetSocketAddress( + env.getProperty("mqtt.external.network.host", "localhost"), + env.getProperty("mqtt.external.network.port", int.class, 1883)); + + externalNetwork.start(address); + externalNetwork.onAccept(externalConnectionConsumer); + + return address; + } + + @Bean + InetSocketAddress internalNetworkAddress( + ServerNetwork internalNetwork, + Consumer internalConnectionConsumer) { + + var address = new InetSocketAddress( + env.getProperty("mqtt.internal.network.host", "localhost"), + env.getProperty("mqtt.internal.network.port", int.class, 11883)); + + internalNetwork.start(address); + internalNetwork.onAccept(internalConnectionConsumer); + + return address; + } + + @Bean + Consumer externalConnectionConsumer() { + return mqttConnection -> { + log.info("Accepted external connection: {}", mqttConnection); + var client = (UnsafeMqttClient) mqttConnection.getClient(); + mqttConnection.onReceive((conn, packet) -> client.handle(packet)); + }; + } + + @Bean + Consumer internalConnectionConsumer() { + return mqttConnection -> { + log.info("Accepted internal connection: {}", mqttConnection); + var client = (UnsafeMqttClient) mqttConnection.getClient(); + mqttConnection.onReceive((conn, packet) -> client.handle(packet)); + }; + } + + @Bean + MqttConnectionConfig externalConnectionConfig() { + return new MqttConnectionConfig( + QoS.of(env.getProperty("mqtt.connection.max.qos", int.class, 2)), + env.getProperty( + "mqtt.external.connection.max.packet.size", + int.class, + MqttProperties.MAXIMUM_PACKET_SIZE_DEFAULT), + env.getProperty( + "mqtt.external.connection.min.keep.alive", + int.class, + MqttProperties.SERVER_KEEP_ALIVE_DEFAULT), + env.getProperty( + "mqtt.external.connection.receive.maximum", + int.class, + MqttProperties.RECEIVE_MAXIMUM_DEFAULT), + env.getProperty( + "mqtt.external.connection.topic.alias.maximum", + int.class, + MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED), + env.getProperty( + "mqtt.external.connection.default.session.expiration.time", + long.class, + MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT), + env.getProperty( + "mqtt.external.connection.keep.alive.enabled", + boolean.class, + MqttProperties.KEEP_ALIVE_ENABLED_DEFAULT), + env.getProperty( + "mqtt.external.connection.sessions.enabled", + boolean.class, + MqttProperties.SESSIONS_ENABLED_DEFAULT), + env.getProperty( + "mqtt.external.connection.retain.available", + boolean.class, + MqttProperties.RETAIN_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.external.connection.wildcard.subscription.available", + boolean.class, + MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.external.connection.subscription.id.available", + boolean.class, + MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.external.connection.shared.subscription.available", + boolean.class, + MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT)); + } + + @Bean + MqttConnectionConfig internalConnectionConfig() { + return new MqttConnectionConfig( + QoS.of(env.getProperty("mqtt.internal.connection.max.qos", int.class, 2)), + env.getProperty( + "mqtt.internal.connection.max.packet.size", + int.class, + MqttProperties.MAXIMUM_PACKET_SIZE_DEFAULT), + env.getProperty( + "mqtt.internal.connection.min.keep.alive", + int.class, + MqttProperties.SERVER_KEEP_ALIVE_DEFAULT), + env.getProperty( + "mqtt.internal.connection.receive.maximum", + int.class, + MqttProperties.RECEIVE_MAXIMUM_DEFAULT), + env.getProperty( + "mqtt.internal.connection.topic.alias.maximum", + int.class, + MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED), + env.getProperty( + "mqtt.internal.connection.default.session.expiration.time", + long.class, + MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT), + env.getProperty( + "mqtt.internal.connection.keep.alive.enabled", + boolean.class, + MqttProperties.KEEP_ALIVE_ENABLED_DEFAULT), + env.getProperty( + "mqtt.internal.connection.sessions.enabled", + boolean.class, + MqttProperties.SESSIONS_ENABLED_DEFAULT), + env.getProperty( + "mqtt.internal.connection.retain.available", + boolean.class, + MqttProperties.RETAIN_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.internal.connection.wildcard.subscription.available", + boolean.class, + MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.internal.connection.subscription.id.available", + boolean.class, + MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT), + env.getProperty( + "mqtt.internal.connection.shared.subscription.available", + boolean.class, + MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT)); + } + + private ChannelFactory externalConnectionFactory( + BufferAllocator bufferAllocator, + MqttConnectionConfig connectionConfig, + PacketInHandler[] packetHandlers, + MqttClientReleaseHandler releaseHandler) { + return connectionFactory( + bufferAllocator, + connectionConfig, + packetHandlers, + releaseHandler, + ExternalMqttClient::new); + } + + private ChannelFactory internalConnectionFactory( + BufferAllocator bufferAllocator, + MqttConnectionConfig connectionConfig, + PacketInHandler[] packetHandlers, + MqttClientReleaseHandler releaseHandler) { + return connectionFactory( + bufferAllocator, + connectionConfig, + packetHandlers, + releaseHandler, + InternalMqttClient::new); + } + + private ChannelFactory connectionFactory( + BufferAllocator bufferAllocator, + MqttConnectionConfig connectionConfig, + PacketInHandler[] packetHandlers, + MqttClientReleaseHandler releaseHandler, + BiFunction clientFactory) { + return (network, channel) -> new MqttConnection( + network, + channel, + bufferAllocator, + 100, + packetHandlers, + connectionConfig, + mqttConnection -> clientFactory.apply(mqttConnection, releaseHandler)); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/exception/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/config/package-info.java similarity index 59% rename from src/main/java/com/ss/mqtt/broker/exception/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/config/package-info.java index 6df6b4ad..e6af1260 100644 --- a/src/main/java/com/ss/mqtt/broker/exception/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/config/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.exception; +package javasabr.mqtt.legacy.config; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/exception/ConnectionRejectException.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/ConnectionRejectException.java new file mode 100644 index 00000000..5e485ba4 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/ConnectionRejectException.java @@ -0,0 +1,18 @@ +package javasabr.mqtt.legacy.exception; + +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import lombok.Getter; + +public class ConnectionRejectException extends MqttException { + + private final @Getter ConnectAckReasonCode reasonCode; + + public ConnectionRejectException(ConnectAckReasonCode reasonCode) { + this.reasonCode = reasonCode; + } + + public ConnectionRejectException(Throwable cause, ConnectAckReasonCode reasonCode) { + super(cause); + this.reasonCode = reasonCode; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/exception/CredentialsSourceException.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/CredentialsSourceException.java new file mode 100644 index 00000000..db39942a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/CredentialsSourceException.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.legacy.exception; + +public class CredentialsSourceException extends RuntimeException { + + public CredentialsSourceException(String message) { + super(message); + } + + public CredentialsSourceException(Throwable cause) { + super(cause); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/exception/InconsistentSubscriptionStateException.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/InconsistentSubscriptionStateException.java new file mode 100644 index 00000000..01ceca7e --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/InconsistentSubscriptionStateException.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.legacy.exception; + +public class InconsistentSubscriptionStateException extends RuntimeException { + + public InconsistentSubscriptionStateException(String message) { + super(message); + } + + public InconsistentSubscriptionStateException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/exception/MalformedPacketMqttException.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/MalformedPacketMqttException.java similarity index 75% rename from src/main/java/com/ss/mqtt/broker/exception/MalformedPacketMqttException.java rename to legacy/src/main/java/javasabr/mqtt/legacy/exception/MalformedPacketMqttException.java index 996f48df..f7312050 100644 --- a/src/main/java/com/ss/mqtt/broker/exception/MalformedPacketMqttException.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/MalformedPacketMqttException.java @@ -1,4 +1,4 @@ -package com.ss.mqtt.broker.exception; +package javasabr.mqtt.legacy.exception; import lombok.NoArgsConstructor; diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/exception/MqttException.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/MqttException.java new file mode 100644 index 00000000..a6b9766d --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/MqttException.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.legacy.exception; + +public class MqttException extends RuntimeException { + + public MqttException() {} + + public MqttException(String message) { + super(message); + } + + public MqttException(Throwable cause) { + super(cause); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/exception/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/exception/package-info.java new file mode 100644 index 00000000..ef0e5fce --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/exception/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.exception; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/AbstractMqttClientReleaseHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/AbstractMqttClientReleaseHandler.java new file mode 100644 index 00000000..38c07ff8 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/AbstractMqttClientReleaseHandler.java @@ -0,0 +1,61 @@ +package javasabr.mqtt.legacy.handler.client; + +import javasabr.mqtt.legacy.network.client.AbstractMqttClient; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.service.ClientIdRegistry; +import javasabr.mqtt.legacy.service.MqttSessionService; +import javasabr.mqtt.legacy.service.SubscriptionService; +import javasabr.rlib.common.util.StringUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import reactor.core.publisher.Mono; + +@Log4j2 +@RequiredArgsConstructor +public abstract class AbstractMqttClientReleaseHandler implements + MqttClientReleaseHandler { + + private final ClientIdRegistry clientIdRegistry; + private final MqttSessionService sessionService; + private final SubscriptionService subscriptionService; + + @Override + public Mono release(UnsafeMqttClient client) { + var clientId = client.getClientId(); + //noinspection unchecked + return releaseImpl((T) client).doOnNext(aVoid -> log.info("Client {} was released", clientId)); + } + + protected Mono releaseImpl(T client) { + + var clientId = client.getClientId(); + client.setClientId(StringUtils.EMPTY); + + if (StringUtils.isEmpty(clientId)) { + log.warn("This client {} is already released or rejected", client); + return Mono.empty(); + } + + var session = client.getSession(); + + Mono asyncActions = null; + + if (session != null) { + subscriptionService.cleanSubscriptions(client, session); + if (client + .getConnectionConfig() + .isSessionsEnabled()) { + asyncActions = sessionService.store(clientId, session, client.getSessionExpiryInterval()); + client.setSession(null); + } + } + + if (asyncActions != null) { + asyncActions = asyncActions.flatMap(any -> clientIdRegistry.unregister(clientId)); + } else { + asyncActions = clientIdRegistry.unregister(clientId); + } + + return asyncActions; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/DefaultMqttClientReleaseHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/DefaultMqttClientReleaseHandler.java new file mode 100644 index 00000000..f552a335 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/DefaultMqttClientReleaseHandler.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.legacy.handler.client; + +import javasabr.mqtt.legacy.network.client.ExternalMqttClient; +import javasabr.mqtt.legacy.service.ClientIdRegistry; +import javasabr.mqtt.legacy.service.MqttSessionService; +import javasabr.mqtt.legacy.service.SubscriptionService; + +public class DefaultMqttClientReleaseHandler extends AbstractMqttClientReleaseHandler { + + public DefaultMqttClientReleaseHandler( + ClientIdRegistry clientIdRegistry, + MqttSessionService sessionService, + SubscriptionService subscriptionService) { + super(clientIdRegistry, sessionService, subscriptionService); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/MqttClientReleaseHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/MqttClientReleaseHandler.java new file mode 100644 index 00000000..fa9244da --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/MqttClientReleaseHandler.java @@ -0,0 +1,9 @@ +package javasabr.mqtt.legacy.handler.client; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import reactor.core.publisher.Mono; + +public interface MqttClientReleaseHandler { + + Mono release(UnsafeMqttClient client); +} diff --git a/src/main/java/com/ss/mqtt/broker/model/data/type/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/package-info.java similarity index 55% rename from src/main/java/com/ss/mqtt/broker/model/data/type/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/client/package-info.java index 482991d1..f335d6cb 100644 --- a/src/main/java/com/ss/mqtt/broker/model/data/type/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/client/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.model.data.type; +package javasabr.mqtt.legacy.handler.client; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/AbstractPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/AbstractPacketHandler.java new file mode 100644 index 00000000..7c360de1 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/AbstractPacketHandler.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; + +public abstract class AbstractPacketHandler implements + PacketInHandler { + + @Override + public void handle(UnsafeMqttClient client, MqttReadablePacket packet) { + //noinspection unchecked + handleImpl((C) client, (R) packet); + } + + protected abstract void handleImpl(C client, R packet); +} + diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/ConnectInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/ConnectInPacketHandler.java new file mode 100644 index 00000000..214bd34a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/ConnectInPacketHandler.java @@ -0,0 +1,195 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import static javasabr.mqtt.model.MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; +import static javasabr.mqtt.model.MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; +import static javasabr.mqtt.model.MqttProperties.SERVER_KEEP_ALIVE_DISABLED; +import static javasabr.mqtt.model.MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED; +import static javasabr.mqtt.model.MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; +import static javasabr.mqtt.model.MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED; +import static javasabr.mqtt.model.MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED; +import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; +import static javasabr.mqtt.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; +import static javasabr.mqtt.legacy.util.ReactorUtils.ifTrue; + +import javasabr.mqtt.legacy.exception.ConnectionRejectException; +import javasabr.mqtt.legacy.exception.MalformedPacketMqttException; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.ConnectInPacket; +import javasabr.mqtt.legacy.service.AuthenticationService; +import javasabr.mqtt.legacy.service.ClientIdRegistry; +import javasabr.mqtt.legacy.service.MqttSessionService; +import javasabr.mqtt.legacy.service.SubscriptionService; +import javasabr.rlib.common.util.StringUtils; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import reactor.core.publisher.Mono; + +@Log4j2 +@RequiredArgsConstructor +public class ConnectInPacketHandler extends AbstractPacketHandler { + + private final ClientIdRegistry clientIdRegistry; + private final AuthenticationService authenticationService; + private final MqttSessionService mqttSessionService; + private final SubscriptionService subscriptionService; + + @Override + protected void handleImpl(UnsafeMqttClient client, ConnectInPacket packet) { + + var connection = client.getConnection(); + connection.setMqttVersion(packet.getMqttVersion()); + + if (checkPacketException(client, packet)) { + return; + } + + authenticationService + .auth(packet.getUsername(), packet.getPassword()) + .flatMap(ifTrue(client, packet, this::registerClient, BAD_USER_NAME_OR_PASSWORD, client::reject)) + .flatMap(ifTrue(client, packet, this::restoreSession, CLIENT_IDENTIFIER_NOT_VALID, client::reject)) + .subscribe(); + } + + private Mono registerClient(UnsafeMqttClient client, ConnectInPacket packet) { + + var requestedClientId = packet.getClientId(); + + if (StringUtils.isNotEmpty(requestedClientId)) { + return clientIdRegistry + .register(requestedClientId) + .map(ifTrue(requestedClientId, client::setClientId)); + } else { + + var mqttVersion = client + .getConnection() + .getMqttVersion(); + + // we can't assign generated client if for mqtt version less than 5 + if (mqttVersion.ordinal() < MqttVersion.MQTT_5.ordinal()) { + return Mono.just(false); + } + + return clientIdRegistry + .generate() + .flatMap(newClientId -> clientIdRegistry + .register(newClientId) + .map(ifTrue(newClientId, client::setClientId))); + } + } + + private Mono restoreSession(UnsafeMqttClient client, ConnectInPacket packet) { + + if (packet.isCleanStart()) { + return mqttSessionService + .create(client.getClientId()) + .flatMap(session -> onConnected(client, packet, session, false)); + } else { + return mqttSessionService + .restore(client.getClientId()) + .flatMap(session -> onConnected(client, packet, session, true)) + .switchIfEmpty(Mono.defer(() -> mqttSessionService + .create(client.getClientId()) + .flatMap(session -> onConnected(client, packet, session, false)))); + } + } + + private Mono onConnected( + UnsafeMqttClient client, + ConnectInPacket packet, + MqttSession session, + boolean sessionRestored) { + + var connection = client.getConnection(); + var config = connection.getConfig(); + + // if it was closed in parallel + if (connection.isClosed() && config.isSessionsEnabled()) { + // store the session again + return mqttSessionService.store(client.getClientId(), session, config.getDefaultSessionExpiryInterval()); + } + + // select result keep alive time + var minimalKeepAliveTime = Math.max(config.getMinKeepAliveTime(), packet.getKeepAlive()); + var keepAlive = config.isKeepAliveEnabled() ? minimalKeepAliveTime : SERVER_KEEP_ALIVE_DISABLED; + + // select result session expiry interval + var sessionExpiryInterval = config.isSessionsEnabled() + ? packet.getSessionExpiryInterval() + : SESSION_EXPIRY_INTERVAL_DISABLED; + + if (sessionExpiryInterval == SESSION_EXPIRY_INTERVAL_UNDEFINED) { + sessionExpiryInterval = config.getDefaultSessionExpiryInterval(); + } + + // select result receive max + var receiveMax = packet.getReceiveMax() == RECEIVE_MAXIMUM_UNDEFINED + ? config.getReceiveMaximum() + : Math.min(packet.getReceiveMax(), config.getReceiveMaximum()); + + // select result maximum packet size + var maximumPacketSize = packet.getMaximumPacketSize() == MAXIMUM_PACKET_SIZE_UNDEFINED + ? config.getMaximumPacketSize() + : Math.min(packet.getMaximumPacketSize(), config.getMaximumPacketSize()); + + // select result topic alias maximum + var topicAliasMaximum = packet.getTopicAliasMaximum() == TOPIC_ALIAS_MAXIMUM_UNDEFINED + ? TOPIC_ALIAS_MAXIMUM_DISABLED + : Math.min(packet.getTopicAliasMaximum(), config.getTopicAliasMaximum()); + + client.setSession(session); + client.configure( + sessionExpiryInterval, + receiveMax, + maximumPacketSize, + topicAliasMaximum, + keepAlive, + packet.isRequestResponseInformation(), + packet.isRequestProblemInformation()); + + var connectAck = client + .getPacketOutFactory() + .newConnectAck( + client, + ConnectAckReasonCode.SUCCESS, + sessionRestored, + packet.getClientId(), + packet.getSessionExpiryInterval(), + packet.getKeepAlive(), + packet.getReceiveMax()); + + subscriptionService.restoreSubscriptions(client, session); + + return Mono.fromFuture(client + .sendWithFeedback(connectAck) + .thenApply(result -> onSentConnAck(client, session, result))); + } + + private boolean onSentConnAck(UnsafeMqttClient client, MqttSession session, boolean result) { + + if (!result) { + log.warn("Was issue with sending conn ack packet to client {}", client.getClientId()); + return false; + } + + session.resendPendingPackets(client); + return true; + } + + private boolean checkPacketException(UnsafeMqttClient client, ConnectInPacket packet) { + + var exception = packet.getException(); + + if (exception instanceof ConnectionRejectException) { + client.reject(((ConnectionRejectException) exception).getReasonCode()); + return true; + } else if (exception instanceof MalformedPacketMqttException) { + client.reject(ConnectAckReasonCode.MALFORMED_PACKET); + return true; + } + + return false; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/DisconnetInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/DisconnetInPacketHandler.java new file mode 100644 index 00000000..b29b82f0 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/DisconnetInPacketHandler.java @@ -0,0 +1,26 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.DisconnectInPacket; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class DisconnetInPacketHandler extends AbstractPacketHandler { + + @Override + protected void handleImpl(UnsafeMqttClient client, DisconnectInPacket packet) { + + var reasonCode = packet.getReasonCode(); + + if (reasonCode == DisconnectReasonCode.NORMAL_DISCONNECTION) { + log.info("Disconnect client {}", client); + } else { + log.error("Disconnect client {} by error reason {}", client, reasonCode); + } + + client + .getConnection() + .close(); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PacketInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PacketInHandler.java new file mode 100644 index 00000000..3b7ea97a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PacketInHandler.java @@ -0,0 +1,11 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; + +public interface PacketInHandler { + + PacketInHandler EMPTY = (client, packet) -> {}; + + void handle(MqttClient.UnsafeMqttClient client, MqttReadablePacket packet); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PendingOutResponseInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PendingOutResponseInPacketHandler.java new file mode 100644 index 00000000..ea92381b --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PendingOutResponseInPacketHandler.java @@ -0,0 +1,19 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class PendingOutResponseInPacketHandler extends + AbstractPacketHandler { + + @Override + protected void handleImpl(UnsafeMqttClient client, R packet) { + var session = client.getSession(); + if (session != null) { + session.updateOutPendingPacket(client, packet); + } + } +} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishAckInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishAckInPacketHandler.java similarity index 59% rename from src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishAckInPacketHandler.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishAckInPacketHandler.java index 720a7605..daa1dbb7 100644 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishAckInPacketHandler.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishAckInPacketHandler.java @@ -1,6 +1,6 @@ -package com.ss.mqtt.broker.handler.packet.in; +package javasabr.mqtt.legacy.handler.packet.in; -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishCompleteInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishCompleteInPacketHandler.java similarity index 60% rename from src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishCompleteInPacketHandler.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishCompleteInPacketHandler.java index b773c34f..5af6fdfd 100644 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishCompleteInPacketHandler.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishCompleteInPacketHandler.java @@ -1,6 +1,6 @@ -package com.ss.mqtt.broker.handler.packet.in; +package javasabr.mqtt.legacy.handler.packet.in; -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishInPacketHandler.java new file mode 100644 index 00000000..f27ba206 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishInPacketHandler.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.service.PublishingService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class PublishInPacketHandler extends AbstractPacketHandler { + + private final PublishingService publishingService; + + @Override + protected void handleImpl(UnsafeMqttClient client, PublishInPacket packet) { + publishingService.publish(client, packet); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReceiveInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReceiveInPacketHandler.java similarity index 60% rename from src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReceiveInPacketHandler.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReceiveInPacketHandler.java index 8fc92f52..b29ce8cf 100644 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReceiveInPacketHandler.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReceiveInPacketHandler.java @@ -1,6 +1,6 @@ -package com.ss.mqtt.broker.handler.packet.in; +package javasabr.mqtt.legacy.handler.packet.in; -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReleaseInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReleaseInPacketHandler.java new file mode 100644 index 00000000..5508da93 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/PublishReleaseInPacketHandler.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class PublishReleaseInPacketHandler extends AbstractPacketHandler { + + @Override + protected void handleImpl(UnsafeMqttClient client, PublishReleaseInPacket packet) { + var session = client.getSession(); + if (session != null) { + session.updateInPendingPacket(client, packet); + } + } +} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/SubscribeInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/SubscribeInPacketHandler.java similarity index 68% rename from src/main/java/com/ss/mqtt/broker/handler/packet/in/SubscribeInPacketHandler.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/SubscribeInPacketHandler.java index 6e6f69ce..0af71989 100644 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/SubscribeInPacketHandler.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/SubscribeInPacketHandler.java @@ -1,20 +1,19 @@ -package com.ss.mqtt.broker.handler.packet.in; +package javasabr.mqtt.legacy.handler.packet.in; -import static com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; -import static com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; +import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; +import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; import static java.lang.Byte.toUnsignedInt; -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.SubscribeInPacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.service.SubscriptionService; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.SubscribeInPacket; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.legacy.service.SubscriptionService; +import java.util.Set; import javasabr.rlib.collections.array.Array; import lombok.RequiredArgsConstructor; -import java.util.Set; - @RequiredArgsConstructor public class SubscribeInPacketHandler extends AbstractPacketHandler { diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/UnsubscribeInPacketHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/UnsubscribeInPacketHandler.java new file mode 100644 index 00000000..a7ebb04f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/UnsubscribeInPacketHandler.java @@ -0,0 +1,20 @@ +package javasabr.mqtt.legacy.handler.packet.in; + +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeInPacket; +import javasabr.mqtt.legacy.service.SubscriptionService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class UnsubscribeInPacketHandler extends AbstractPacketHandler { + + private final SubscriptionService subscriptionService; + + @Override + protected void handleImpl(UnsafeMqttClient client, UnsubscribeInPacket packet) { + var ackReasonCodes = subscriptionService.unsubscribe(client, packet.getTopicFilters()); + client.send(client + .getPacketOutFactory() + .newUnsubscribeAck(packet.getPacketId(), ackReasonCodes)); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/factory/packet/out/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/package-info.java similarity index 53% rename from src/main/java/com/ss/mqtt/broker/factory/packet/out/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/package-info.java index 1eea1d12..27719391 100644 --- a/src/main/java/com/ss/mqtt/broker/factory/packet/out/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/packet/in/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.factory.packet.out; +package javasabr.mqtt.legacy.handler.packet.in; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/AbstractPublishInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/AbstractPublishInHandler.java new file mode 100644 index 00000000..d450d233 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/AbstractPublishInHandler.java @@ -0,0 +1,36 @@ +package javasabr.mqtt.legacy.handler.publish.in; + +import javasabr.mqtt.legacy.handler.publish.out.PublishOutHandler; +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.service.SubscriptionService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +abstract class AbstractPublishInHandler implements PublishInHandler { + + protected final SubscriptionService subscriptionService; + protected final PublishOutHandler[] publishOutHandlers; + + public void handle(MqttClient client, PublishInPacket packet) { + handleResult( + client, + packet, + subscriptionService.forEachTopicSubscriber(packet.getTopicName(), packet, this::sendToSubscriber)); + } + + private ActionResult sendToSubscriber(SingleSubscriber subscriber, PublishInPacket packet) { + return publishOutHandler(subscriber.getQos()).handle(packet, subscriber); + } + + private PublishOutHandler publishOutHandler(QoS qos) { + return publishOutHandlers[qos.ordinal()]; + } + + protected void handleResult(MqttClient client, PublishInPacket packet, ActionResult result) { + // nothing to do + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/PublishInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/PublishInHandler.java new file mode 100644 index 00000000..906b61ae --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/PublishInHandler.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.legacy.handler.publish.in; + +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +/** + * Interface to handle incoming publish packets. + */ +public interface PublishInHandler { + + void handle(MqttClient client, PublishInPacket packet); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos0PublishInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos0PublishInHandler.java new file mode 100644 index 00000000..bc3c9dba --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos0PublishInHandler.java @@ -0,0 +1,11 @@ +package javasabr.mqtt.legacy.handler.publish.in; + +import javasabr.mqtt.legacy.handler.publish.out.PublishOutHandler; +import javasabr.mqtt.legacy.service.SubscriptionService; + +public class Qos0PublishInHandler extends AbstractPublishInHandler { + + public Qos0PublishInHandler(SubscriptionService subscriptionService, PublishOutHandler[] publishOutHandlers) { + super(subscriptionService, publishOutHandlers); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos1PublishInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos1PublishInHandler.java new file mode 100644 index 00000000..069670c0 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos1PublishInHandler.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.legacy.handler.publish.in; + +import javasabr.mqtt.legacy.handler.publish.out.PublishOutHandler; +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.service.SubscriptionService; + +public class Qos1PublishInHandler extends AbstractPublishInHandler { + + public Qos1PublishInHandler(SubscriptionService subscriptionService, PublishOutHandler[] publishOutHandlers) { + super(subscriptionService, publishOutHandlers); + } + + @Override + public void handle(MqttClient client, PublishInPacket packet) { + + var session = client.getSession(); + + // it means this client was already closed + if (session == null) { + return; + } + + super.handle(client, packet); + } + + @Override + protected void handleResult(MqttClient client, PublishInPacket packet, ActionResult result) { + + PublishAckReasonCode reasonCode; + + switch (result) { + case EMPTY: + reasonCode = PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS; + break; + case SUCCESS: + reasonCode = PublishAckReasonCode.SUCCESS; + break; + default: + reasonCode = PublishAckReasonCode.UNSPECIFIED_ERROR; + break; + } + + client.send(client + .getPacketOutFactory() + .newPublishAck(packet.getPacketId(), reasonCode)); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos2PublishInHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos2PublishInHandler.java new file mode 100644 index 00000000..61ebb67f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/Qos2PublishInHandler.java @@ -0,0 +1,85 @@ +package javasabr.mqtt.legacy.handler.publish.in; + +import javasabr.mqtt.legacy.handler.publish.out.PublishOutHandler; +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket; +import javasabr.mqtt.legacy.service.SubscriptionService; + +public class Qos2PublishInHandler extends AbstractPublishInHandler implements MqttSession.PendingPacketHandler { + + public Qos2PublishInHandler(SubscriptionService subscriptionService, PublishOutHandler[] publishOutHandlers) { + super(subscriptionService, publishOutHandlers); + } + + @Override + public void handle(MqttClient client, PublishInPacket packet) { + + var session = client.getSession(); + + // it means this client was already closed + if (session == null) { + return; + } + + // if this packet is re-try from client + if (packet.isDuplicate()) { + // if this packet was accepted before then we can skip it + if (session.hasInPending(packet.getPacketId())) { + return; + } + } + + super.handle(client, packet); + } + + @Override + protected void handleResult(MqttClient client, PublishInPacket packet, ActionResult result) { + + // because it was checked + final MqttSession session = client.getSession(); + + // it means this client was already closed + if (session == null) { + return; + } + + PublishReceivedReasonCode reasonCode; + + switch (result) { + case EMPTY: + reasonCode = PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS; + break; + case SUCCESS: + reasonCode = PublishReceivedReasonCode.SUCCESS; + break; + default: + reasonCode = PublishReceivedReasonCode.UNSPECIFIED_ERROR; + break; + } + + session.registerInPublish(packet, this, packet.getPacketId()); + + client.send(client + .getPacketOutFactory() + .newPublishReceived(packet.getPacketId(), reasonCode)); + } + + @Override + public boolean handleResponse(MqttClient client, HasPacketId response) { + + if (!(response instanceof PublishReleaseInPacket)) { + throw new IllegalStateException("Unexpected response " + response); + } + + var packetOutFactory = client.getPacketOutFactory(); + client.send(packetOutFactory.newPublishCompleted(response.getPacketId(), PublishCompletedReasonCode.SUCCESS)); + + return true; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/package-info.java new file mode 100644 index 00000000..341529bb --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/in/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.handler.publish.in; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/AbstractPublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/AbstractPublishOutHandler.java new file mode 100644 index 00000000..86c0af0b --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/AbstractPublishOutHandler.java @@ -0,0 +1,54 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +abstract class AbstractPublishOutHandler implements PublishOutHandler { + + @Override + public ActionResult handle(PublishInPacket packet, SingleSubscriber subscriber) { + + MqttClient client = (MqttClient) subscriber.getUser(); + var session = client.getSession(); + + // if session is null it means this client was already closed + if (session != null) { + return handleImpl(packet, subscriber, client, session); + } else { + return ActionResult.EMPTY; + } + } + + protected abstract ActionResult handleImpl( + PublishInPacket packet, + Subscriber subscriber, + MqttClient client, + MqttSession session); + + protected abstract QoS getQoS(); + + void sendPublish(MqttClient client, PublishInPacket packet, int packetId, boolean duplicate) { + + var packetOutFactory = client.getPacketOutFactory(); + client.send(packetOutFactory.newPublish( + packetId, + getQoS(), + packet.isRetained(), + duplicate, + packet + .getTopicName() + .toString(), + MqttProperties.TOPIC_ALIAS_NOT_SET, + packet.getPayload(), + packet.isPayloadFormatIndicator(), + packet.getResponseTopic(), + packet.getCorrelationData(), + packet.getUserProperties())); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PersistentPublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PersistentPublishOutHandler.java new file mode 100644 index 00000000..c278d97f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PersistentPublishOutHandler.java @@ -0,0 +1,34 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +public abstract class PersistentPublishOutHandler extends AbstractPublishOutHandler implements + MqttSession.PendingPacketHandler { + + @Override + protected ActionResult handleImpl( + PublishInPacket packet, + Subscriber subscriber, + MqttClient client, + MqttSession session) { + // generate new uniq packet id per client + var packetId = session.nextPacketId(); + + // register waiting async response + session.registerOutPublish(packet, this, packetId); + + // send publish + sendPublish(client, packet, packetId, false); + + return ActionResult.SUCCESS; + } + + @Override + public void resend(MqttClient client, PublishInPacket packet, int packetId) { + sendPublish(client, packet, packetId, true); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PublishOutHandler.java new file mode 100644 index 00000000..4ababf13 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/PublishOutHandler.java @@ -0,0 +1,13 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +/** + * Interface to handle outgoing publish packets. + */ +public interface PublishOutHandler { + + ActionResult handle(PublishInPacket packet, SingleSubscriber subscriber); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos0PublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos0PublishOutHandler.java new file mode 100644 index 00000000..2a7cb118 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos0PublishOutHandler.java @@ -0,0 +1,28 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import static javasabr.mqtt.model.ActionResult.SUCCESS; + +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +public class Qos0PublishOutHandler extends AbstractPublishOutHandler { + + @Override + protected QoS getQoS() { + return QoS.AT_MOST_ONCE; + } + + @Override + protected ActionResult handleImpl( + PublishInPacket packet, + Subscriber subscriber, + MqttClient client, + MqttSession session) { + sendPublish(client, packet, 0, false); + return SUCCESS; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos1PublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos1PublishOutHandler.java new file mode 100644 index 00000000..6cd039ee --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos1PublishOutHandler.java @@ -0,0 +1,27 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class Qos1PublishOutHandler extends PersistentPublishOutHandler { + + @Override + protected QoS getQoS() { + return QoS.AT_LEAST_ONCE; + } + + @Override + public boolean handleResponse(MqttClient client, HasPacketId response) { + + if (!(response instanceof PublishAckInPacket)) { + throw new IllegalStateException("Unexpected response: " + response); + } + + // just return 'true' to remove pending packet from session + return true; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos2PublishOutHandler.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos2PublishOutHandler.java new file mode 100644 index 00000000..2cabd94b --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/Qos2PublishOutHandler.java @@ -0,0 +1,34 @@ +package javasabr.mqtt.legacy.handler.publish.out; + +import static javasabr.mqtt.model.reason.code.PublishReleaseReasonCode.SUCCESS; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public final class Qos2PublishOutHandler extends PersistentPublishOutHandler { + + @Override + protected QoS getQoS() { + return QoS.EXACTLY_ONCE; + } + + @Override + public boolean handleResponse(MqttClient client, HasPacketId response) { + + var packetOutFactory = client.getPacketOutFactory(); + + if (response instanceof PublishReceivedInPacket) { + client.send(packetOutFactory.newPublishRelease(response.getPacketId(), SUCCESS)); + return false; + } else if (response instanceof PublishCompleteInPacket) { + return true; + } else { + throw new IllegalStateException("Unexpected response: " + response); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/package-info.java new file mode 100644 index 00000000..ee1311c0 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/handler/publish/out/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.handler.publish.out; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/model/impl/DefaultMqttSession.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/DefaultMqttSession.java similarity index 85% rename from src/main/java/com/ss/mqtt/broker/model/impl/DefaultMqttSession.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/DefaultMqttSession.java index b7e057ed..71b437e1 100644 --- a/src/main/java/com/ss/mqtt/broker/model/impl/DefaultMqttSession.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/DefaultMqttSession.java @@ -1,12 +1,13 @@ -package com.ss.mqtt.broker.model.impl; - -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.MqttSession.UnsafeMqttSession; -import com.ss.mqtt.broker.model.SubscribeTopicFilter; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; +package javasabr.mqtt.legacy.network; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.legacy.network.MqttSession.UnsafeMqttSession; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import java.util.Collection; +import java.util.concurrent.atomic.AtomicInteger; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.LockableArray; import javasabr.rlib.functions.TriConsumer; @@ -17,9 +18,6 @@ import lombok.ToString; import lombok.extern.log4j.Log4j2; -import java.util.Collection; -import java.util.concurrent.atomic.AtomicInteger; - @Log4j2 @ToString(of = "clientId") @EqualsAndHashCode(of = "clientId") @@ -98,7 +96,7 @@ public int nextPacketId() { var nextId = packetIdGenerator.incrementAndGet(); - if (nextId >= MqttPropertyConstants.MAXIMUM_PACKET_ID) { + if (nextId >= MqttProperties.MAXIMUM_PACKET_ID) { packetIdGenerator.compareAndSet(nextId, 0); return nextPacketId(); } @@ -161,10 +159,11 @@ public void resendPendingPackets(MqttClient mqttClient) { try { pendingOutPublishes .iterations() - .forEach(mqttClient, (pendingPublish, client) -> { - PendingPacketHandler handler = pendingPublish.handler; - handler.resend(client, pendingPublish.publish, pendingPublish.packetId); - }); + .forEach( + mqttClient, (pendingPublish, client) -> { + PendingPacketHandler handler = pendingPublish.handler; + handler.resend(client, pendingPublish.publish, pendingPublish.packetId); + }); } finally { pendingOutPublishes.readUnlock(stamp); } @@ -194,7 +193,8 @@ public void forEachTopicFilter(A arg1, B arg2, TriConsumer release(); + } + + MqttPacketOutFactory getPacketOutFactory(); + + MqttConnectionConfig getConnectionConfig(); + + String getClientId(); + + @Nullable + MqttSession getSession(); + + int getKeepAlive(); + + int getMaximumPacketSize(); + + int getReceiveMax(); + + int getTopicAliasMaximum(); + + long getSessionExpiryInterval(); + + void send(MqttWritablePacket packet); + + CompletableFuture sendWithFeedback(MqttWritablePacket packet); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttConnection.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttConnection.java new file mode 100644 index 00000000..4105ce71 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttConnection.java @@ -0,0 +1,101 @@ +package javasabr.mqtt.legacy.network; + +import javasabr.mqtt.model.MqttConnectionConfig; +import javasabr.mqtt.legacy.handler.packet.in.PacketInHandler; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.MqttPacketReader; +import javasabr.mqtt.legacy.network.packet.MqttPacketWriter; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import java.nio.channels.AsynchronousSocketChannel; +import java.util.function.Function; +import javasabr.rlib.network.BufferAllocator; +import javasabr.rlib.network.Connection; +import javasabr.rlib.network.Network; +import javasabr.rlib.network.impl.AbstractConnection; +import javasabr.rlib.network.packet.PacketReader; +import javasabr.rlib.network.packet.PacketWriter; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; + +@Log4j2 +public class MqttConnection extends AbstractConnection { + + @Getter(AccessLevel.PROTECTED) + private final PacketReader packetReader; + + @Getter(AccessLevel.PROTECTED) + private final PacketWriter packetWriter; + + @Getter + private final PacketInHandler[] packetHandlers; + + @Getter + private final UnsafeMqttClient client; + @Getter + private final MqttConnectionConfig config; + + @Getter + @Setter + private volatile MqttVersion mqttVersion; + + @Getter + @Setter + private volatile MqttSession session; + + public MqttConnection( + Network> network, + AsynchronousSocketChannel channel, + BufferAllocator bufferAllocator, + int maxPacketsByRead, + PacketInHandler[] packetHandlers, + MqttConnectionConfig config, + Function clientFactory) { + super(network, channel, bufferAllocator, maxPacketsByRead); + this.packetHandlers = packetHandlers; + this.config = config; + this.mqttVersion = MqttVersion.MQTT_3_1_1; + this.packetReader = createPacketReader(); + this.packetWriter = createPacketWriter(); + this.client = clientFactory.apply(this); + } + + public boolean isSupported(MqttVersion mqttVersion) { + return this.mqttVersion.ordinal() >= mqttVersion.ordinal(); + } + + private PacketReader createPacketReader() { + return new MqttPacketReader( + this, + channel, + bufferAllocator, + this::updateLastActivity, + this::handleReceivedPacket, + maxPacketsByRead); + } + + private PacketWriter createPacketWriter() { + return new MqttPacketWriter( + this, + channel, + bufferAllocator, + this::updateLastActivity, + this::nextPacketToWrite, + this::onWrittenPacket, + this::onSentPacket); + } + + @Override + public String toString() { + return getRemoteAddress(); + } + + @Override + protected void doClose() { + client.release().subscribe(); + super.doClose(); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttSession.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttSession.java new file mode 100644 index 00000000..b8c8375e --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/MqttSession.java @@ -0,0 +1,68 @@ +package javasabr.mqtt.legacy.network; + +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.rlib.functions.TriConsumer; + +public interface MqttSession { + + interface UnsafeMqttSession extends MqttSession { + + void setExpirationTime(long expirationTime); + + void clear(); + + void onPersisted(); + + void onRestored(); + } + + interface PendingPacketHandler { + + /** + * @return true if pending packet can be removed. + */ + boolean handleResponse(MqttClient client, HasPacketId response); + + default void resend(MqttClient client, PublishInPacket packet, int packetId) { + } + } + + String getClientId(); + + int nextPacketId(); + + /** + * @return the expiration time in ms or -1 if it should not be expired now. + */ + long getExpirationTime(); + + void resendPendingPackets(MqttClient client); + + boolean hasOutPending(); + + boolean hasInPending(); + + boolean hasInPending(int packetId); + + boolean hasOutPending(int packetId); + + void registerOutPublish(PublishInPacket publish, PendingPacketHandler handler, int packetId); + + void registerInPublish(PublishInPacket publish, PendingPacketHandler handler, int packetId); + + void updateOutPendingPacket(MqttClient client, HasPacketId response); + + void updateInPendingPacket(MqttClient client, HasPacketId response); + + void forEachTopicFilter( + F first, + S second, + TriConsumer consumer); + + void addSubscriber(SubscribeTopicFilter subscribe); + + void removeSubscriber(TopicFilter subscribe); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/client/AbstractMqttClient.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/AbstractMqttClient.java new file mode 100644 index 00000000..c50aaf37 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/AbstractMqttClient.java @@ -0,0 +1,136 @@ +package javasabr.mqtt.legacy.network.client; + +import javasabr.mqtt.legacy.out.MqttPacketOutFactories; +import javasabr.mqtt.model.MqttConnectionConfig; +import javasabr.mqtt.legacy.out.MqttPacketOutFactory; +import javasabr.mqtt.legacy.handler.client.MqttClientReleaseHandler; +import javasabr.mqtt.legacy.handler.packet.in.PacketInHandler; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.MqttClient.UnsafeMqttClient; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.base.utils.DebugUtils; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import org.jspecify.annotations.Nullable; +import reactor.core.publisher.Mono; + +@Getter +@Log4j2 +public abstract class AbstractMqttClient implements UnsafeMqttClient { + + static { + DebugUtils.registerIncludedFields("clientId"); + } + + protected final MqttConnection connection; + protected final MqttClientReleaseHandler releaseHandler; + protected final AtomicBoolean released; + + @Setter + private volatile String clientId; + + @Setter + @Getter + @Nullable + private volatile MqttSession session; + + private volatile long sessionExpiryInterval; + private volatile int receiveMax; + private volatile int maximumPacketSize; + private volatile int topicAliasMaximum; + private volatile int keepAlive; + + private volatile boolean requestResponseInformation = false; + private volatile boolean requestProblemInformation = false; + + public AbstractMqttClient(MqttConnection connection, MqttClientReleaseHandler releaseHandler) { + this.connection = connection; + this.releaseHandler = releaseHandler; + this.released = new AtomicBoolean(false); + this.clientId = StringUtils.EMPTY; + var config = connection.getConfig(); + this.sessionExpiryInterval = config.getDefaultSessionExpiryInterval(); + this.receiveMax = config.getReceiveMaximum(); + this.maximumPacketSize = config.getMaximumPacketSize(); + this.topicAliasMaximum = config.getTopicAliasMaximum(); + this.keepAlive = config.getMinKeepAliveTime(); + } + + @Override + public void handle(MqttReadablePacket packet) { + log.debug("Client [{}] received packet: {} : {}", clientId, packet.getName(), packet); + PacketInHandler packetHandler = connection.getPacketHandlers()[packet.getPacketType()]; + if (packetHandler != null) { + packetHandler.handle(this, packet); + } else { + log.warn("No packet handler in client {} for packet {}", this, packet); + } + } + + @Override + public void configure( + long sessionExpiryInterval, + int receiveMax, + int maximumPacketSize, + int topicAliasMaximum, + int keepAlive, + boolean requestResponseInformation, + boolean requestProblemInformation) { + this.sessionExpiryInterval = sessionExpiryInterval; + this.receiveMax = receiveMax; + this.maximumPacketSize = maximumPacketSize; + this.topicAliasMaximum = topicAliasMaximum; + this.keepAlive = keepAlive; + this.requestProblemInformation = requestProblemInformation; + this.requestResponseInformation = requestResponseInformation; + } + + @Override + public void send(MqttWritablePacket packet) { + log.debug("Send to client [{}] packet: {} : {}", clientId, packet.getName(), packet); + connection.send(packet); + } + + @Override + public CompletableFuture sendWithFeedback(MqttWritablePacket packet) { + log.debug("Send to client [{}] packet: {} : {}", clientId, packet.getName(), packet); + return connection.sendWithFeedback(packet); + } + + public void reject(ConnectAckReasonCode reasonCode) { + connection + .sendWithFeedback(getPacketOutFactory().newConnectAck(this, reasonCode)) + .thenAccept(sent -> connection.close()); + } + + @Override + public MqttPacketOutFactory getPacketOutFactory() { + return MqttPacketOutFactories.of(connection.getMqttVersion()); + } + + @Override + public MqttConnectionConfig getConnectionConfig() { + return connection.getConfig(); + } + + @Override + public Mono release() { + if (released.compareAndSet(false, true)) { + return releaseHandler.release(this); + } else { + return Mono.empty(); + } + } + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/client/ExternalMqttClient.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/ExternalMqttClient.java new file mode 100644 index 00000000..d1e53f98 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/ExternalMqttClient.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.legacy.network.client; + +import javasabr.mqtt.legacy.handler.client.MqttClientReleaseHandler; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.base.utils.DebugUtils; + +public class ExternalMqttClient extends AbstractMqttClient { + + static { + DebugUtils.registerIncludedFields("clientId"); + } + + public ExternalMqttClient(MqttConnection connection, MqttClientReleaseHandler releaseHandler) { + super(connection, releaseHandler); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/client/InternalMqttClient.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/InternalMqttClient.java new file mode 100644 index 00000000..104e4848 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/InternalMqttClient.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.legacy.network.client; + +import javasabr.mqtt.legacy.handler.client.MqttClientReleaseHandler; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.base.utils.DebugUtils; + +public class InternalMqttClient extends AbstractMqttClient { + + static { + DebugUtils.registerIncludedFields("clientId"); + } + + public InternalMqttClient(MqttConnection connection, MqttClientReleaseHandler releaseHandler) { + super(connection, releaseHandler); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/client/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/package-info.java new file mode 100644 index 00000000..3de6cd3f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/client/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.network.client; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/package-info.java new file mode 100644 index 00000000..d5a71711 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.network; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/HasPacketId.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/HasPacketId.java similarity index 59% rename from src/main/java/com/ss/mqtt/broker/network/packet/HasPacketId.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/HasPacketId.java index 3487920a..bf0b4537 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/HasPacketId.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/HasPacketId.java @@ -1,8 +1,8 @@ -package com.ss.mqtt.broker.network.packet; +package javasabr.mqtt.legacy.network.packet; import javasabr.rlib.network.packet.Packet; public interface HasPacketId extends Packet { - int getPacketId(); + int getPacketId(); } diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketReader.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketReader.java similarity index 70% rename from src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketReader.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketReader.java index 8b704f7e..e3dc176a 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketReader.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketReader.java @@ -1,23 +1,25 @@ -package com.ss.mqtt.broker.network.packet; - -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.in.AuthenticationInPacket; -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket; -import com.ss.mqtt.broker.network.packet.in.ConnectInPacket; -import com.ss.mqtt.broker.network.packet.in.DisconnectInPacket; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; -import com.ss.mqtt.broker.network.packet.in.PingRequestInPacket; -import com.ss.mqtt.broker.network.packet.in.PingResponseInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket; -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket; -import com.ss.mqtt.broker.network.packet.in.SubscribeInPacket; -import com.ss.mqtt.broker.network.packet.in.UnsubscribeAckInPacket; -import com.ss.mqtt.broker.network.packet.in.UnsubscribeInPacket; -import com.ss.mqtt.broker.util.MqttDataUtils; +package javasabr.mqtt.legacy.network.packet; + +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.in.AuthenticationInPacket; +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket; +import javasabr.mqtt.legacy.network.packet.in.ConnectInPacket; +import javasabr.mqtt.legacy.network.packet.in.DisconnectInPacket; +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket; +import javasabr.mqtt.legacy.network.packet.in.PingRequestInPacket; +import javasabr.mqtt.legacy.network.packet.in.PingResponseInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket; +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket; +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket; +import javasabr.mqtt.legacy.network.packet.in.SubscribeInPacket; +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeAckInPacket; +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeInPacket; +import javasabr.mqtt.legacy.util.MqttDataUtils; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; import java.util.NoSuchElementException; import java.util.function.Consumer; import javasabr.rlib.common.util.ArrayUtils; @@ -25,9 +27,6 @@ import javasabr.rlib.functions.ByteFunction; import javasabr.rlib.network.BufferAllocator; import javasabr.rlib.network.packet.impl.AbstractPacketReader; - -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousSocketChannel; import org.jspecify.annotations.Nullable; public class MqttPacketReader extends AbstractPacketReader { diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketWriter.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketWriter.java new file mode 100644 index 00000000..48ca1479 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/MqttPacketWriter.java @@ -0,0 +1,82 @@ +package javasabr.mqtt.legacy.network.packet; + +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.legacy.util.MqttDataUtils; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousSocketChannel; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import javasabr.rlib.network.BufferAllocator; +import javasabr.rlib.network.packet.WritablePacket; +import javasabr.rlib.network.packet.impl.AbstractPacketWriter; +import org.jspecify.annotations.Nullable; + +public class MqttPacketWriter extends AbstractPacketWriter { + + public MqttPacketWriter( + MqttConnection connection, + AsynchronousSocketChannel channel, + BufferAllocator bufferAllocator, + Runnable updateActivityFunction, + Supplier<@Nullable WritablePacket> nextWritePacketSupplier, + Consumer writtenPacketHandler, + BiConsumer sentPacketHandler) { + super( + connection, + channel, + bufferAllocator, + updateActivityFunction, + nextWritePacketSupplier, + writtenPacketHandler, + sentPacketHandler); + } + + @Override + protected int getTotalSize(WritablePacket packet, int expectedLength) { + return 1 + MqttDataUtils.sizeOfMbi(expectedLength) + expectedLength; + } + + @Override + protected boolean onBeforeWrite( + MqttWritablePacket packet, + int expectedLength, + int totalSize, + ByteBuffer firstBuffer, + ByteBuffer secondBuffer) { + firstBuffer.clear(); + secondBuffer.clear(); + return true; + } + + @Override + protected boolean onWrite( + MqttWritablePacket packet, + int expectedLength, + int totalSize, + ByteBuffer firstBuffer, + ByteBuffer secondBuffer) { + if (!packet.write(secondBuffer)) { + return false; + } else { + secondBuffer.flip(); + return true; + } + } + + @Override + protected boolean onAfterWrite( + MqttWritablePacket packet, + int expectedLength, + int totalSize, + ByteBuffer firstBuffer, + ByteBuffer secondBuffer) { + firstBuffer.put((byte) packet.getPacketTypeAndFlags()); + MqttDataUtils.writeMbi(secondBuffer.remaining(), firstBuffer); + firstBuffer + .put(secondBuffer) + .flip(); + return true; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/PacketType.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/PacketType.java new file mode 100644 index 00000000..413eebc4 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/PacketType.java @@ -0,0 +1,105 @@ +package javasabr.mqtt.legacy.network.packet; + +public enum PacketType { + RESERVED, + /** + * After a Network Connection is established by a Client to a Server, the first Packet sent from the Client to the + * Server MUST be a CONNECT Packet + */ + CONNECT, + /** + * The CONNACK Packet is the packet sent by the Server in response to a CONNECT Packet received from a Client. The + * first packet sent from the Server to the Client MUST be a CONNACK Packet [MQTT-3.2.0-1]. + */ + CONNECT_ACK, + /** + * A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client to transport an Application + * Message. + */ + PUBLISH, + /** + * A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1. + */ + PUBLISH_ACK, + /** + * A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second packet of the QoS 2 protocol + * exchange. + */ + PUBLISH_RECEIVED, + /** + * A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the QoS 2 protocol exchange. + */ + PUBLISH_RELEASED, + /** + * The PUBCOMP packet is the response to a PUBREL packet. It is the fourth and final packet of the QoS 2 protocol + * exchange. + */ + PUBLISH_COMPLETED, + /** + * The SUBSCRIBE Packet is sent from the Client to the Server to create one or more Subscriptions. Each Subscription + * registers a Client’s interest in one or more Topics. The Server sends PUBLISH Packets to the Client in order to + * forward Application Messages that were published to Topics that match these Subscriptions. The SUBSCRIBE Packet + * also specifies (for each Subscription) the maximum QoS with which the Server can send Application Messages to the + * Client. + */ + SUBSCRIBE, + /** + * A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing of a SUBSCRIBE Packet. + */ + SUBSCRIBE_ACK, + /** + * An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. + */ + UNSUBSCRIBE, + /** + * The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an UNSUBSCRIBE Packet. + */ + UNSUBSCRIBE_ACK, + /** + * The PINGREQ packet is sent from a Client to the Server. It can be used to: + *

+ * • Indicate to the Server that the Client is alive in the absence of any other MQTT Control Packets being sent from + * the Client to the Server. + *

+ * • Request that the Server responds to confirm that it is alive. + *

+ * • Exercise the network to indicate that the Network Connection is active. + *

+ * This packet is used in Keep Alive processing. Refer to section 3.1.2.10 for more details + */ + PING_REQUEST, + /** + * A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ packet. It indicates that the Server + * is alive. + */ + PING_RESPONSE, + /** + * The DISCONNECT packet is the final MQTT Control Packet sent from the Client or the Server. It indicates the reason + * why the Network Connection is being closed. The Client or Server MAY send a DISCONNECT packet before closing the + * Network Connection. If the Network Connection is closed without the Client first sending a DISCONNECT packet with + * Reason Code 0x00 (Normal disconnection) and the Connection has a Will Message, the Will Message is published. Refer + * to section 3.1.2.5 for further details. + */ + DISCONNECT, + /** + * An AUTH packet is sent from Client to Server or Server to Client as part of an extended authentication exchange, + * such as challenge / response authentication. It is a Protocol Error for the Client or Server to send an AUTH packet + * if the CONNECT packet did not contain the same Authentication Method. + */ + AUTHENTICATE, + + /** + * Not supported + */ + INVALID; + + private static final PacketType[] VALUES = values(); + + public static PacketType fromByte(byte packetType) { + if (packetType < 0 || packetType > AUTHENTICATE.ordinal()) { + return INVALID; + } else { + return VALUES[packetType]; + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/AuthenticationInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/AuthenticationInPacket.java new file mode 100644 index 00000000..28d7f5a5 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/AuthenticationInPacket.java @@ -0,0 +1,109 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Authentication exchange. + */ +@Getter +public class AuthenticationInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.AUTHENTICATE.ordinal(); + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol + Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more + information about extended authentication. + */ + PacketProperty.AUTHENTICATION_METHOD, + /* + Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication + Data more than once. The contents of this data are defined by the authentication method. Refer to + section 4.12 for more information about extended authentication. + */ + PacketProperty.AUTHENTICATION_DATA, + /* + Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is + human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. + + The sender MUST NOT send this property if it would increase the size of the AUTH packet beyond the + Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the + Reason String more than once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the AUTH packet + beyond the Maximum Packet Size specified by the receiver [MQTT-3.15.2-3]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private AuthenticateReasonCode reasonCode; + + // properties + private String reason; + private String authenticationMethod; + + private byte[] authenticationData; + + public AuthenticationInPacket(byte info) { + super(info); + this.reasonCode = AuthenticateReasonCode.SUCCESS; + this.reason = StringUtils.EMPTY; + this.authenticationMethod = StringUtils.EMPTY; + this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901219 + reasonCode = AuthenticateReasonCode.of(readUnsignedByte(buffer)); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, byte[] value) { + switch (property) { + case AUTHENTICATION_DATA: + authenticationData = value; + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + case AUTHENTICATION_METHOD: + authenticationMethod = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectAckInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectAckInPacket.java similarity index 61% rename from src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectAckInPacket.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectAckInPacket.java index f26dbe82..d0828147 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectAckInPacket.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectAckInPacket.java @@ -1,20 +1,21 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.*; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; import javasabr.rlib.collections.array.MutableArray; import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.NumberUtils; import javasabr.rlib.common.util.StringUtils; import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; /** * Acknowledge connection request. @@ -22,9 +23,9 @@ @Getter public class ConnectAckInPacket extends MqttReadablePacket { - private static final byte PACKET_TYPE = (byte) PacketType.CONNECT_ACK.ordinal(); + private static final byte PACKET_TYPE = (byte) PacketType.CONNECT_ACK.ordinal(); - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Session Expiry Interval in seconds. It is a Protocol Error to include the Session Expiry Interval more than once. @@ -32,7 +33,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { If the Session Expiry Interval is absent the value in the CONNECT Packet used. The server uses this property to inform the Client that it is using a value other than that sent by the Client in the CONNACK. */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + PacketProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -43,7 +44,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { If the Receive Maximum value is absent, then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM, + PacketProperty.RECEIVE_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum @@ -64,7 +65,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { reject the connection. It SHOULD use a CONNACK packet with Reason Code 0x9B (QoS not supported) as described in section 4.13 Handling errors, and MUST close the Network Connection */ - PacketProperty.MAXIMUM_QOS, + PacketProperty.MAXIMUM_QOS, /* Followed by a Byte field. If present, this byte declares whether the Server supports retained messages. A value of 0 means that retained messages are not supported. A value of 1 means retained messages are @@ -81,7 +82,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { Server SHOULD send a DISCONNECT with Reason Code of 0x9A (Retain not supported) as described in section 4.13. */ - PacketProperty.RETAIN_AVAILABLE, + PacketProperty.RETAIN_AVAILABLE, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Server is willing to accept. If the Maximum Packet Size is not present, there is no limit on the packet size imposed beyond the @@ -98,7 +99,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { a Server receives a packet whose size exceeds this limit, this is a Protocol Error, the Server uses DISCONNECT with Reason Code 0x95 (Packet too large), as described in section 4.13. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + PacketProperty.MAXIMUM_PACKET_SIZE, /* Followed by the UTF-8 string which is the Assigned Client Identifier. It is a Protocol Error to include the Assigned Client Identifier more than once. @@ -110,7 +111,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { containing an Assigned Client Identifier. The Assigned Client Identifier MUST be a new Client Identifier not used by any other Session currently in the Server [ */ - PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, + PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -123,7 +124,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { on this connection. If Topic Alias Maximum is absent or 0, the Client MUST NOT send any Topic Aliases on to the Server */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + PacketProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -133,7 +134,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { property if it would increase the size of the CONNACK packet beyond the Maximum Packet Size specified by the Client [MQTT-3.2.2-19]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + PacketProperty.REASON_STRING, /* Followed by a UTF-8 String Pair. This property can be used to provide additional information to the Client including diagnostic information. The Server MUST NOT send this property if it would increase the size of @@ -144,7 +145,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { The content and meaning of this property is not defined by this specification. The receiver of a CONNACK containing this property MAY ignore it. */ - PacketProperty.USER_PROPERTY, + PacketProperty.USER_PROPERTY, /* Followed by a Byte field. If present, this byte declares whether the Server supports Wildcard Subscriptions. A value is 0 means that Wildcard Subscriptions are not supported. A value of 1 means @@ -162,7 +163,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { Wildcard Subscription. In this case the Server MAY send a SUBACK Control Packet with a Reason Code 0xA2 (Wildcard Subscriptions not supported). */ - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Subscription Identifiers. A value is 0 means that Subscription Identifiers are not supported. A value of 1 means @@ -174,7 +175,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { Subscription Identifiers, this is a Protocol Error. The Server uses DISCONNECT with Reason Code of 0xA1 (Subscription Identifiers not supported) as described in section 4.13. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Shared Subscriptions. A value is 0 means that Shared Subscriptions are not supported. A value of 1 means Shared @@ -185,7 +186,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { Shared Subscriptions, this is a Protocol Error. The Server uses DISCONNECT with Reason Code 0x9E (Shared Subscriptions not supported) as described in section 4.13. */ - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, + PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, /* Followed by a Two Byte Integer with the Keep Alive time assigned by the Server. If the Server sends a Server Keep Alive on the CONNACK packet, the Client MUST use this value instead of the Keep Alive @@ -193,7 +194,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { the Server MUST use the Keep Alive value set by the Client on CONNECT [MQTT-3.2.2-22]. It is a Protocol Error to include the Server Keep Alive more than once. */ - PacketProperty.SERVER_KEEP_ALIVE, + PacketProperty.SERVER_KEEP_ALIVE, /* Followed by a UTF-8 Encoded String which is used as the basis for creating a Response Topic. The way in which the Client creates a Response Topic from the Response Information is not defined by this @@ -202,7 +203,7 @@ public class ConnectAckInPacket extends MqttReadablePacket { If the Client sends a Request Response Information with a value 1, it is OPTIONAL for the Server to send the Response Information in the CONNACK. */ - PacketProperty.RESPONSE_INFORMATION, + PacketProperty.RESPONSE_INFORMATION, /* Followed by a UTF-8 Encoded String which can be used by the Client to identify another Server to use. It is a Protocol Error to include the Server Reference more than once. @@ -212,189 +213,182 @@ public class ConnectAckInPacket extends MqttReadablePacket { Refer to section 4.11 Server redirection for information about how Server Reference is used */ - PacketProperty.SERVER_REFERENCE, + PacketProperty.SERVER_REFERENCE, /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to include the Authentication Method more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + PacketProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. The contents of this data are defined by the authentication method and the state of already exchanged authentication data. It is a Protocol Error to include the Authentication Data more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA - ); - - /** - * The values the Connect Reason Code are shown below. If a well formed CONNECT packet is received - * by the Server, but the Server is unable to complete the Connection the Server MAY send a CONNACK - * packet containing the appropriate Connect Reason code from this table. If a Server sends a CONNACK - * packet containing a Reason code of 128 or greater it MUST then close the Network Connection - */ - private ConnectAckReasonCode reasonCode; - private QoS maximumQos; - - /** - * The Session Present flag informs the Client whether the Server is using Session State from a - * previous connection for this ClientID. - * This allows the Client and Server to have a consistent view of the Session State. - * If the Server accepts a connection with Clean Start set to 1, the Server MUST set Session - * Present to 0 in the CONNACK packet in addition to setting a 0x00 (Success) Reason Code in the CONNACK packet - */ - private boolean sessionPresent; - - // properties - private String assignedClientId; - private String reason; - private String responseInformation; - private String authenticationMethod; - private String serverReference; - private byte[] authenticationData; - - private long sessionExpiryInterval; - - private int receiveMax; - private int maximumPacketSize; - private int topicAliasMaximum; - private int serverKeepAlive; - - private boolean retainAvailable; - private boolean wildcardSubscriptionAvailable; - private boolean sharedSubscriptionAvailable; - private boolean subscriptionIdAvailable; - - public ConnectAckInPacket(byte info) { - super(info); - this.userProperties = MutableArray.ofType(StringPair.class); - this.reasonCode = ConnectAckReasonCode.SUCCESS; - this.maximumQos = QoS.EXACTLY_ONCE; - this.retainAvailable = MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT; - this.assignedClientId = StringUtils.EMPTY; - this.reason = StringUtils.EMPTY; - this.sharedSubscriptionAvailable = MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT; - this.wildcardSubscriptionAvailable = MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT; - this.subscriptionIdAvailable = MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT; - this.responseInformation = StringUtils.EMPTY; - this.serverReference = StringUtils.EMPTY; - this.authenticationMethod = StringUtils.EMPTY; - this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; - this.serverKeepAlive = MqttPropertyConstants.SERVER_KEEP_ALIVE_UNDEFINED; - this.maximumPacketSize = MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED; - this.sessionExpiryInterval = MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED; - this.topicAliasMaximum = MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED; - this.receiveMax = MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED; + PacketProperty.AUTHENTICATION_DATA); + + /** + * The values the Connect Reason Code are shown below. If a well formed CONNECT packet is received by the Server, but + * the Server is unable to complete the Connection the Server MAY send a CONNACK packet containing the appropriate + * Connect Reason code from this table. If a Server sends a CONNACK packet containing a Reason code of 128 or greater + * it MUST then close the Network Connection + */ + private ConnectAckReasonCode reasonCode; + private QoS maximumQos; + + /** + * The Session Present flag informs the Client whether the Server is using Session State from a previous connection + * for this ClientID. This allows the Client and Server to have a consistent view of the Session State. If the Server + * accepts a connection with Clean Start set to 1, the Server MUST set Session Present to 0 in the CONNACK packet in + * addition to setting a 0x00 (Success) Reason Code in the CONNACK packet + */ + private boolean sessionPresent; + + // properties + private String assignedClientId; + private String reason; + private String responseInformation; + private String authenticationMethod; + private String serverReference; + private byte[] authenticationData; + + private long sessionExpiryInterval; + + private int receiveMax; + private int maximumPacketSize; + private int topicAliasMaximum; + private int serverKeepAlive; + + private boolean retainAvailable; + private boolean wildcardSubscriptionAvailable; + private boolean sharedSubscriptionAvailable; + private boolean subscriptionIdAvailable; + + public ConnectAckInPacket(byte info) { + super(info); + this.userProperties = MutableArray.ofType(StringPair.class); + this.reasonCode = ConnectAckReasonCode.SUCCESS; + this.maximumQos = QoS.EXACTLY_ONCE; + this.retainAvailable = MqttProperties.RETAIN_AVAILABLE_DEFAULT; + this.assignedClientId = StringUtils.EMPTY; + this.reason = StringUtils.EMPTY; + this.sharedSubscriptionAvailable = MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT; + this.wildcardSubscriptionAvailable = MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT; + this.subscriptionIdAvailable = MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT; + this.responseInformation = StringUtils.EMPTY; + this.serverReference = StringUtils.EMPTY; + this.authenticationMethod = StringUtils.EMPTY; + this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; + this.serverKeepAlive = MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED; + this.maximumPacketSize = MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; + this.sessionExpiryInterval = MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; + this.topicAliasMaximum = MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED; + this.receiveMax = MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035 + sessionPresent = readUnsignedByte(buffer) == 1; + reasonCode = ConnectAckReasonCode.of(connection.isSupported(MqttVersion.MQTT_5), readUnsignedByte(buffer)); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, byte[] value) { + switch (property) { + case AUTHENTICATION_DATA: + authenticationData = value; + break; + default: + unexpectedProperty(property); } - - @Override - public byte getPacketType() { - return PACKET_TYPE; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + case ASSIGNED_CLIENT_IDENTIFIER: + assignedClientId = value; + break; + case RESPONSE_INFORMATION: + responseInformation = value; + break; + case AUTHENTICATION_METHOD: + authenticationMethod = value; + break; + case SERVER_REFERENCE: + serverReference = value; + break; + default: + unexpectedProperty(property); } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035 - sessionPresent = readUnsignedByte(buffer) == 1; - reasonCode = ConnectAckReasonCode.of(connection.isSupported(MqttVersion.MQTT_5), readUnsignedByte(buffer)); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, byte[] value) { - switch (property) { - case AUTHENTICATION_DATA: - authenticationData = value; - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - case ASSIGNED_CLIENT_IDENTIFIER: - assignedClientId = value; - break; - case RESPONSE_INFORMATION: - responseInformation = value; - break; - case AUTHENTICATION_METHOD: - authenticationMethod = value; - break; - case SERVER_REFERENCE: - serverReference = value; - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(@NotNull PacketProperty property, long value) { - switch (property) { - case WILDCARD_SUBSCRIPTION_AVAILABLE: - wildcardSubscriptionAvailable = NumberUtils.toBoolean(value); - break; - case SHARED_SUBSCRIPTION_AVAILABLE: - sharedSubscriptionAvailable = NumberUtils.toBoolean(value); - break; - case SUBSCRIPTION_IDENTIFIER_AVAILABLE: - subscriptionIdAvailable = NumberUtils.toBoolean(value); - break; - case RETAIN_AVAILABLE: - retainAvailable = NumberUtils.toBoolean(value); - break; - case RECEIVE_MAXIMUM: - receiveMax = (int) NumberUtils.validate( - value, - MqttPropertyConstants.RECEIVE_MAXIMUM_MIN, - MqttPropertyConstants.RECEIVE_MAXIMUM_MAX - ); - break; - case MAXIMUM_QOS: - maximumQos = QoS.of((int) value); - break; - case SERVER_KEEP_ALIVE: - serverKeepAlive = NumberUtils.validate( - (int) value, - MqttPropertyConstants.SERVER_KEEP_ALIVE_MIN, - MqttPropertyConstants.SERVER_KEEP_ALIVE_MAX - ); - break; - case TOPIC_ALIAS_MAXIMUM: - topicAliasMaximum = NumberUtils.validate( - (int) value, - MqttPropertyConstants.TOPIC_ALIAS_MIN, - MqttPropertyConstants.TOPIC_ALIAS_MAX - ); - break; - case SESSION_EXPIRY_INTERVAL: - sessionExpiryInterval = NumberUtils.validate( - value, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_MIN, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_INFINITY - ); - break; - case MAXIMUM_PACKET_SIZE: - maximumPacketSize = NumberUtils.validate( - (int) value, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_MIN, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_MAX - ); - break; - default: - unexpectedProperty(property); - } + } + + @Override + protected void applyProperty(PacketProperty property, long value) { + switch (property) { + case WILDCARD_SUBSCRIPTION_AVAILABLE: + wildcardSubscriptionAvailable = NumberUtils.toBoolean(value); + break; + case SHARED_SUBSCRIPTION_AVAILABLE: + sharedSubscriptionAvailable = NumberUtils.toBoolean(value); + break; + case SUBSCRIPTION_IDENTIFIER_AVAILABLE: + subscriptionIdAvailable = NumberUtils.toBoolean(value); + break; + case RETAIN_AVAILABLE: + retainAvailable = NumberUtils.toBoolean(value); + break; + case RECEIVE_MAXIMUM: + receiveMax = (int) NumberUtils.validate( + value, + MqttProperties.RECEIVE_MAXIMUM_MIN, + MqttProperties.RECEIVE_MAXIMUM_MAX); + break; + case MAXIMUM_QOS: + maximumQos = QoS.of((int) value); + break; + case SERVER_KEEP_ALIVE: + serverKeepAlive = NumberUtils.validate( + (int) value, + MqttProperties.SERVER_KEEP_ALIVE_MIN, + MqttProperties.SERVER_KEEP_ALIVE_MAX); + break; + case TOPIC_ALIAS_MAXIMUM: + topicAliasMaximum = NumberUtils.validate( + (int) value, + MqttProperties.TOPIC_ALIAS_MIN, + MqttProperties.TOPIC_ALIAS_MAX); + break; + case SESSION_EXPIRY_INTERVAL: + sessionExpiryInterval = NumberUtils.validate( + value, + MqttProperties.SESSION_EXPIRY_INTERVAL_MIN, + MqttProperties.SESSION_EXPIRY_INTERVAL_INFINITY); + break; + case MAXIMUM_PACKET_SIZE: + maximumPacketSize = NumberUtils.validate( + (int) value, + MqttProperties.MAXIMUM_PACKET_SIZE_MIN, + MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + break; + default: + unexpectedProperty(property); } + } } diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectInPacket.java similarity index 56% rename from src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectInPacket.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectInPacket.java index aabaeb3d..f604df29 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/ConnectInPacket.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/ConnectInPacket.java @@ -1,24 +1,22 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.exception.ConnectionRejectException; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.legacy.exception.ConnectionRejectException; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; import javasabr.rlib.collections.array.MutableArray; import javasabr.rlib.common.util.ArrayUtils; import javasabr.rlib.common.util.NumberUtils; import javasabr.rlib.common.util.StringUtils; import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; /** * Connection request. @@ -26,13 +24,13 @@ @Getter public class ConnectInPacket extends MqttReadablePacket { - private static final byte PACKET_TYPE = (byte) PacketType.CONNECT.ordinal(); + private static final byte PACKET_TYPE = (byte) PacketType.CONNECT.ordinal(); - static { - DebugUtils.registerIncludedFields("clientId", "keepAlive", "cleanStart"); - } + static { + DebugUtils.registerIncludedFields("clientId", "keepAlive", "cleanStart"); + } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent the value 0 is used. If it is set to 0, or is absent, the Session ends when the Network Connection is closed. @@ -41,7 +39,7 @@ public class ConnectInPacket extends MqttReadablePacket { The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + PacketProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -52,7 +50,7 @@ public class ConnectInPacket extends MqttReadablePacket { The value of Receive Maximum applies only to the current Network Connection. If the Receive Maximum value is absent then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM, + PacketProperty.RECEIVE_MAXIMUM, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Client is willing to accept. If the Maximum Packet Size is not present, no limit on the packet size is imposed beyond the limitations in @@ -61,7 +59,7 @@ public class ConnectInPacket extends MqttReadablePacket { It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + PacketProperty.MAXIMUM_PACKET_SIZE, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -74,7 +72,7 @@ public class ConnectInPacket extends MqttReadablePacket { this connection. If Topic Alias Maximum is absent or zero, the Server MUST NOT send any Topic Aliases to the Client [MQTT-3.1.2-27]. */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + PacketProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is Protocol Error to include the Request Response Information more than once, or to have a value other than 0 or 1. If the Request Response Information is @@ -84,7 +82,7 @@ public class ConnectInPacket extends MqttReadablePacket { value of 0 indicates that the Server MUST NOT return Response Information [MQTT-3.1.2-28]. If the value is 1 the Server MAY return Response Information in the CONNACK packet. */ - PacketProperty.REQUEST_RESPONSE_INFORMATION, + PacketProperty.REQUEST_RESPONSE_INFORMATION, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Request Problem Information more than once, or to have a value other than 0 or 1. If the Request Problem Information is @@ -103,12 +101,12 @@ public class ConnectInPacket extends MqttReadablePacket { If this value is 1, the Server MAY return a Reason String or User Properties on any packet where it is allowed. */ - PacketProperty.REQUEST_PROBLEM_INFORMATION, + PacketProperty.REQUEST_PROBLEM_INFORMATION, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY, + PacketProperty.USER_PROPERTY, /* Followed by a UTF-8 Encoded String containing the name of the authentication method used for extended authentication .It is a Protocol Error to include Authentication Method more than once. @@ -117,7 +115,7 @@ public class ConnectInPacket extends MqttReadablePacket { If a Client sets an Authentication Method in the CONNECT, the Client MUST NOT send any packets other than AUTH or DISCONNECT packets until it has received a CONNACK packet */ - PacketProperty.AUTHENTICATION_METHOD, + PacketProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data if there is no Authentication Method. It is a Protocol Error to include Authentication Data more than @@ -126,10 +124,9 @@ public class ConnectInPacket extends MqttReadablePacket { The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA - ); + PacketProperty.AUTHENTICATION_DATA); - private static final Set WILL_PROPERTIES = EnumSet.of( + private static final Set WILL_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Will Delay Interval in seconds. It is a Protocol Error to include the Will Delay Interval more than once. If the Will Delay Interval is absent, the default value is 0 @@ -139,7 +136,7 @@ public class ConnectInPacket extends MqttReadablePacket { Session ends, whichever happens first. If a new Network Connection to this Session is made before the Will Delay Interval has passed, the Server MUST NOT send the Will Message */ - PacketProperty.WILL_DELAY_INTERVAL, + PacketProperty.WILL_DELAY_INTERVAL, /* Followed by the value of the Payload Format Indicator, either of: • 0 (0x00) Byte Indicates that the Will Message is unspecified bytes, which is equivalent to not @@ -151,7 +148,7 @@ public class ConnectInPacket extends MqttReadablePacket { It is a Protocol Error to include the Payload Format Indicator more than once. The Server MAY validate that the Will Message is of the format indicated, and if it is not se */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + PacketProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. It is a Protocol Error to include the Message Expiry Interval more than once. @@ -161,19 +158,19 @@ public class ConnectInPacket extends MqttReadablePacket { If absent, no Message Expiry Interval is sent when the Server publishes the Will Message. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + PacketProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by a UTF-8 Encoded String describing the content of the Will Message. It is a Protocol Error to include the Content Type more than once. The value of the Content Type is defined by the sending and receiving application. */ - PacketProperty.CONTENT_TYPE, + PacketProperty.CONTENT_TYPE, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. It is a Protocol Error to include the Response Topic more than once. The presence of a Response Topic identifies the Will Message as a Request. */ - PacketProperty.RESPONSE_TOPIC, + PacketProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include @@ -183,117 +180,117 @@ public class ConnectInPacket extends MqttReadablePacket { The value of the Correlation Data only has meaning to the sender of the Request Message and receiver of the Response Message. */ - PacketProperty.CORRELATION_DATA, + PacketProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. The Server MUST maintain the order of User Properties when publishing the Will Message */ - PacketProperty.USER_PROPERTY - ); - - private MqttVersion mqttVersion; - - private String clientId; - private String willTopic; - private String username; - private byte[] password; - - private byte[] willPayload; - - private int keepAlive; - private int willQos; - private boolean willRetain; - private boolean cleanStart; - - private boolean hasUserName; - private boolean hasPassword; - private boolean willFlag; - - // properties - private String authenticationMethod; - private byte[] authenticationData; - - private long sessionExpiryInterval; - private int receiveMax; - private int maximumPacketSize; - private int topicAliasMaximum; - private boolean requestResponseInformation; - private boolean requestProblemInformation; - - public ConnectInPacket(byte info) { - super(info); - this.userProperties = MutableArray.ofType(StringPair.class); - this.mqttVersion = MqttVersion.MQTT_3_1_1; - this.clientId = StringUtils.EMPTY; - this.willTopic = StringUtils.EMPTY; - this.username = StringUtils.EMPTY; - this.password = ArrayUtils.EMPTY_BYTE_ARRAY; - this.authenticationMethod = StringUtils.EMPTY; - this.willPayload = ArrayUtils.EMPTY_BYTE_ARRAY; - this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; - this.sessionExpiryInterval = MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED; - this.receiveMax = MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED; - this.maximumPacketSize = MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED; - this.topicAliasMaximum = MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED; - this.requestResponseInformation = false; - this.requestProblemInformation = false; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; + PacketProperty.USER_PROPERTY); + + private MqttVersion mqttVersion; + + private String clientId; + private String willTopic; + private String username; + private byte[] password; + + private byte[] willPayload; + + private int keepAlive; + private int willQos; + private boolean willRetain; + private boolean cleanStart; + + private boolean hasUserName; + private boolean hasPassword; + private boolean willFlag; + + // properties + private String authenticationMethod; + private byte[] authenticationData; + + private long sessionExpiryInterval; + private int receiveMax; + private int maximumPacketSize; + private int topicAliasMaximum; + private boolean requestResponseInformation; + private boolean requestProblemInformation; + + public ConnectInPacket(byte info) { + super(info); + this.userProperties = MutableArray.ofType(StringPair.class); + this.mqttVersion = MqttVersion.MQTT_3_1_1; + this.clientId = StringUtils.EMPTY; + this.willTopic = StringUtils.EMPTY; + this.username = StringUtils.EMPTY; + this.password = ArrayUtils.EMPTY_BYTE_ARRAY; + this.authenticationMethod = StringUtils.EMPTY; + this.willPayload = ArrayUtils.EMPTY_BYTE_ARRAY; + this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; + this.sessionExpiryInterval = MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED; + this.receiveMax = MqttProperties.RECEIVE_MAXIMUM_UNDEFINED; + this.maximumPacketSize = MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED; + this.topicAliasMaximum = MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED; + this.requestResponseInformation = false; + this.requestProblemInformation = false; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030 + var protocolName = readString(buffer); + var protocolLevel = buffer.get(); + + mqttVersion = MqttVersion.of(protocolName, protocolLevel); + + if (mqttVersion == MqttVersion.UNKNOWN) { + throw new ConnectionRejectException(ConnectAckReasonCode.UNSUPPORTED_PROTOCOL_VERSION); } - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030 - var protocolName = readString(buffer); - var protocolLevel = buffer.get(); - - mqttVersion = MqttVersion.of(protocolName, protocolLevel); + var flags = readUnsignedByte(buffer); - if (mqttVersion == MqttVersion.UNKNOWN) { - throw new ConnectionRejectException(ConnectAckReasonCode.UNSUPPORTED_PROTOCOL_VERSION); - } + willRetain = NumberUtils.isSetBit(flags, 5); + willQos = (flags & 0x18) >> 3; + cleanStart = NumberUtils.isSetBit(flags, 1); - var flags = readUnsignedByte(buffer); + // for mqtt 3.1.1+ + if (mqttVersion.ordinal() >= MqttVersion.MQTT_3_1_1.ordinal()) { - willRetain = NumberUtils.isSetBit(flags, 5); - willQos = (flags & 0x18) >> 3; - cleanStart = NumberUtils.isSetBit(flags, 1); + var zeroReservedFlag = NumberUtils.isNotSetBit(flags, 0); - // for mqtt 3.1.1+ - if (mqttVersion.ordinal() >= MqttVersion.MQTT_3_1_1.ordinal()) { - - var zeroReservedFlag = NumberUtils.isNotSetBit(flags, 0); - - if (!zeroReservedFlag) { + if (!zeroReservedFlag) { /* The Server MUST validate that the reserved flag in the CONNECT packet is set to 0 [MQTT-3.1.2-3]. If - the reserved flag is not 0 it is a Malformed Packet. Refer to section 4.13 for information about handling + the reserved flag is not 0 it is a Malformed Packet. Refer to section 4.13 for information about + handling errors. */ - throw new ConnectionRejectException(ConnectAckReasonCode.MALFORMED_PACKET); - } - } - - hasUserName = NumberUtils.isSetBit(flags, 7); - hasPassword = NumberUtils.isSetBit(flags, 6); + throw new ConnectionRejectException(ConnectAckReasonCode.MALFORMED_PACKET); + } + } - // for mqtt < 5 we cannot have password without user - if (mqttVersion.ordinal() < MqttVersion.MQTT_5.ordinal() && !hasUserName && hasPassword) { - throw new ConnectionRejectException(ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD); - } + hasUserName = NumberUtils.isSetBit(flags, 7); + hasPassword = NumberUtils.isSetBit(flags, 6); - willFlag = NumberUtils.isSetBit(flags, 2); - keepAlive = readUnsignedShort(buffer); + // for mqtt < 5 we cannot have password without user + if (mqttVersion.ordinal() < MqttVersion.MQTT_5.ordinal() && !hasUserName && hasPassword) { + throw new ConnectionRejectException(ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD); } - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + willFlag = NumberUtils.isSetBit(flags, 2); + keepAlive = readUnsignedShort(buffer); + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { /* The ClientID MUST be present and is the first field in the CONNECT packet Payload @@ -313,100 +310,96 @@ protected void readPayload(MqttConnection connection, ByteBuffer buffer) { If the Server rejects the ClientID it MAY respond to the CONNECT packet with a CONNACK using Reason Code 0x85 (Client Identifier not valid) and then it MUST close the Network Connection */ - clientId = readString(buffer); - - if (willFlag && mqttVersion.ordinal() >= MqttVersion.MQTT_5.ordinal()) { - readProperties(buffer, WILL_PROPERTIES); - } + clientId = readString(buffer); - if (willFlag) { - willTopic = readString(buffer); - } - - if (willFlag) { - willPayload = readBytes(buffer); - } - - if (hasUserName) { - username = readString(buffer); - } - - if (hasPassword) { - password = readBytes(buffer); - } + if (willFlag && mqttVersion.ordinal() >= MqttVersion.MQTT_5.ordinal()) { + readProperties(buffer, WILL_PROPERTIES); } - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - return mqttVersion.ordinal() >= MqttVersion.MQTT_5.ordinal(); + if (willFlag) { + willTopic = readString(buffer); } - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; + if (willFlag) { + willPayload = readBytes(buffer); } - @Override - protected void applyProperty(PacketProperty property, @NotNull byte[] value) { - switch (property) { - case AUTHENTICATION_DATA: - authenticationData = value; - break; - default: - unexpectedProperty(property); - } + if (hasUserName) { + username = readString(buffer); } - @Override - protected void applyProperty(PacketProperty property, @NotNull String value) { - switch (property) { - case AUTHENTICATION_METHOD: - authenticationMethod = value; - break; - default: - unexpectedProperty(property); - } + if (hasPassword) { + password = readBytes(buffer); } - - @Override - protected void applyProperty(PacketProperty property, long value) { - switch (property) { - case REQUEST_RESPONSE_INFORMATION: - requestResponseInformation = NumberUtils.toBoolean(value); - break; - case REQUEST_PROBLEM_INFORMATION: - requestProblemInformation = NumberUtils.toBoolean(value); - break; - case RECEIVE_MAXIMUM: - receiveMax = NumberUtils.validate( - (int) value, - MqttPropertyConstants.RECEIVE_MAXIMUM_MIN, - MqttPropertyConstants.RECEIVE_MAXIMUM_MAX - ); - break; - case TOPIC_ALIAS_MAXIMUM: - topicAliasMaximum = NumberUtils.validate( - (int) value, - MqttPropertyConstants.TOPIC_ALIAS_MIN, - MqttPropertyConstants.TOPIC_ALIAS_MAX - ); - break; - case SESSION_EXPIRY_INTERVAL: - sessionExpiryInterval = NumberUtils.validate( - value, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_MIN, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_INFINITY - ); - break; - case MAXIMUM_PACKET_SIZE: - maximumPacketSize = NumberUtils.validate( - (int) value, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_MIN, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_MAX - ); - break; - default: - unexpectedProperty(property); - } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + return mqttVersion.ordinal() >= MqttVersion.MQTT_5.ordinal(); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, byte[] value) { + switch (property) { + case AUTHENTICATION_DATA: + authenticationData = value; + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case AUTHENTICATION_METHOD: + authenticationMethod = value; + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, long value) { + switch (property) { + case REQUEST_RESPONSE_INFORMATION: + requestResponseInformation = NumberUtils.toBoolean(value); + break; + case REQUEST_PROBLEM_INFORMATION: + requestProblemInformation = NumberUtils.toBoolean(value); + break; + case RECEIVE_MAXIMUM: + receiveMax = NumberUtils.validate( + (int) value, + MqttProperties.RECEIVE_MAXIMUM_MIN, + MqttProperties.RECEIVE_MAXIMUM_MAX); + break; + case TOPIC_ALIAS_MAXIMUM: + topicAliasMaximum = NumberUtils.validate( + (int) value, + MqttProperties.TOPIC_ALIAS_MIN, + MqttProperties.TOPIC_ALIAS_MAX); + break; + case SESSION_EXPIRY_INTERVAL: + sessionExpiryInterval = NumberUtils.validate( + value, + MqttProperties.SESSION_EXPIRY_INTERVAL_MIN, + MqttProperties.SESSION_EXPIRY_INTERVAL_INFINITY); + break; + case MAXIMUM_PACKET_SIZE: + maximumPacketSize = NumberUtils.validate( + (int) value, + MqttProperties.MAXIMUM_PACKET_SIZE_MIN, + MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + break; + default: + unexpectedProperty(property); } + } } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/DisconnectInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/DisconnectInPacket.java new file mode 100644 index 00000000..66a29435 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/DisconnectInPacket.java @@ -0,0 +1,132 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Disconnect notification. + */ +@Getter +public class DisconnectInPacket extends MqttReadablePacket { + + public static final byte PACKET_TYPE = (byte) PacketType.DISCONNECT.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. + + The Session Expiry Interval MUST NOT be sent on a DISCONNECT by the Server [MQTT-3.14.2-2]. + + If the Session Expiry Interval in the CONNECT packet was zero, then it is a Protocol Error to set a non + zero Session Expiry Interval in the DISCONNECT packet sent by the Client. If such a non-zero Session + Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server + uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in + */ + PacketProperty.SESSION_EXPIRY_INTERVAL, + /* + The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet + beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to + include the Reason String more than once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT + packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-4]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY, + /* + The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another + 2601 server)} or 0x9D (Server moved) as described in section 4.13. + */ + PacketProperty.SERVER_REFERENCE); + + private DisconnectReasonCode reasonCode; + + // properties + private String reason; + private String serverReference; + + private long sessionExpiryInterval; + + public DisconnectInPacket(byte info) { + super(info); + this.reasonCode = DisconnectReasonCode.NORMAL_DISCONNECTION; + this.reason = StringUtils.EMPTY; + this.serverReference = StringUtils.EMPTY; + this.sessionExpiryInterval = MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readImpl(MqttConnection connection, ByteBuffer buffer) { + this.sessionExpiryInterval = connection + .getClient() + .getSessionExpiryInterval(); + super.readImpl(connection, buffer); + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 + if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { + reasonCode = DisconnectReasonCode.of(readUnsignedByte(buffer)); + } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + return connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining(); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, long value) { + switch (property) { + case SESSION_EXPIRY_INTERVAL: + sessionExpiryInterval = value; + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + case SERVER_REFERENCE: + serverReference = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/MqttReadablePacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/MqttReadablePacket.java new file mode 100644 index 00000000..c82213b0 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/MqttReadablePacket.java @@ -0,0 +1,253 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.legacy.exception.ConnectionRejectException; +import javasabr.mqtt.legacy.exception.MalformedPacketMqttException; +import javasabr.mqtt.legacy.exception.MqttException; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.base.utils.DebugUtils; +import javasabr.mqtt.legacy.util.MqttDataUtils; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.Set; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.network.packet.impl.AbstractReadablePacket; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; + +public abstract class MqttReadablePacket extends AbstractReadablePacket { + + static { + DebugUtils.registerIncludedFields("userProperties"); + } + + private static final MutableArray EMPTY_PROPERTIES = MutableArray.ofType(StringPair.class); + + @Getter + @RequiredArgsConstructor + private static class Utf8Decoder { + private final CharsetDecoder decoder; + private final ByteBuffer inBuffer; + private final CharBuffer outBuffer; + } + + private static final ThreadLocal LOCAL_DECODER = ThreadLocal.withInitial(() -> { + + var decoder = StandardCharsets.UTF_8 + .newDecoder() + .onMalformedInput(CodingErrorAction.REPORT) + .onUnmappableCharacter(CodingErrorAction.REPORT); + + return new Utf8Decoder(decoder, ByteBuffer.allocate(1024), CharBuffer.allocate(1024)); + }); + + /** + * The list of user properties. + */ + @Getter + protected MutableArray userProperties; + + /** + * The happened exception during parsing this packet. + */ + @Getter + @Nullable + protected Exception exception; + + protected MqttReadablePacket(byte info) { + this.userProperties = EMPTY_PROPERTIES; + } + + public abstract byte getPacketType(); + + @Override + protected void readImpl(MqttConnection connection, ByteBuffer buffer) { + readVariableHeader(connection, buffer); + + if (isPropertiesSupported(connection, buffer)) { + readProperties(buffer); + } + + readPayload(connection, buffer); + } + + @Override + protected void handleException(ByteBuffer buffer, Exception exception) { + super.handleException(buffer, exception); + + if (!(exception instanceof MqttException)) { + exception = new ConnectionRejectException(exception, ConnectAckReasonCode.PROTOCOL_ERROR); + } + + this.exception = exception; + } + + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + return connection.isSupported(MqttVersion.MQTT_5); + } + + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + } + + protected void readProperties(ByteBuffer buffer) { + readProperties(buffer, getAvailableProperties()); + } + + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + } + + protected void readProperties(ByteBuffer buffer, Set availableProperties) { + + var propertiesLength = MqttDataUtils.readMbi(buffer); + + if (propertiesLength == -1) { + throw new IllegalStateException("Can't read properties length."); + } else if (propertiesLength == 0) { + return; + } + + var lastPositionInBuffer = buffer.position() + propertiesLength; + + while (buffer.position() < lastPositionInBuffer) { + + var property = PacketProperty.of(readUnsignedByte(buffer)); + + if (!availableProperties.contains(property)) { + throw new IllegalStateException("Property: " + property + " is not available for this packet."); + } + + switch (property.getDataType()) { + case BYTE: + applyProperty(property, readUnsignedByte(buffer)); + break; + case SHORT: + applyProperty(property, readUnsignedShort(buffer)); + break; + case INTEGER: + applyProperty(property, readUnsignedInt(buffer)); + break; + case MULTI_BYTE_INTEGER: + applyProperty(property, MqttDataUtils.readMbi(buffer)); + break; + case UTF_8_STRING: + applyProperty(property, readString(buffer)); + break; + case UTF_8_STRING_PAIR: + applyProperty(property, new StringPair(readString(buffer), readString(buffer))); + break; + case BINARY: + applyProperty(property, readBytes(buffer)); + break; + default: + throw new IllegalArgumentException("Unsupported data type: " + property.getDataType()); + } + } + } + + protected Set getAvailableProperties() { + return Collections.emptySet(); + } + + protected void applyProperty(PacketProperty property, long value) { + } + + protected void applyProperty(PacketProperty property, String value) { + } + + protected void applyProperty(PacketProperty property, byte[] value) { + } + + protected void applyProperty(PacketProperty property, StringPair value) { + switch (property) { + case USER_PROPERTY: + if (userProperties == EMPTY_PROPERTIES) { + userProperties = MutableArray.ofType(StringPair.class); + } + userProperties.add(value); + break; + } + } + + protected int readUnsignedByte(ByteBuffer buffer) { + return Byte.toUnsignedInt(buffer.get()); + } + + protected int readUnsignedShort(ByteBuffer buffer) { + return Short.toUnsignedInt(buffer.getShort()); + } + + protected long readUnsignedInt(ByteBuffer buffer) { + return Integer.toUnsignedLong(buffer.getInt()); + } + + @Override + protected String readString(ByteBuffer buffer) { + + var utf8Decoder = LOCAL_DECODER.get(); + var inBuffer = utf8Decoder.getInBuffer(); + + var stringLength = readShort(buffer) & 0xFFFF; + + if (stringLength > inBuffer.capacity()) { + throw new MalformedPacketMqttException(); + } + + var decoder = utf8Decoder.getDecoder(); + var outBuffer = utf8Decoder.getOutBuffer(); + + buffer.get( + inBuffer + .clear() + .array(), 0, stringLength); + + decoder.reset(); + + var result = decoder.decode( + inBuffer + .position(stringLength) + .flip(), outBuffer.clear(), true); + + if (result.isError()) { + throw new MalformedPacketMqttException(); + } + + return new String(inBuffer.array(), 0, stringLength, StandardCharsets.UTF_8); + } + + protected byte[] readBytes(ByteBuffer buffer) { + var data = new byte[readShort(buffer) & 0xFFFF]; + buffer.get(data); + return data; + } + + protected byte[] readPayload(ByteBuffer buffer) { + + var payloadSize = buffer.limit() - buffer.position(); + + if (payloadSize < 1) { + return ArrayUtils.EMPTY_BYTE_ARRAY; + } + + var data = new byte[payloadSize]; + buffer.get(data); + return data; + } + + protected void unexpectedProperty(PacketProperty property) { + throw new IllegalArgumentException("Unsupported property: " + property); + } + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingRequestInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingRequestInPacket.java new file mode 100644 index 00000000..0d7ea523 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingRequestInPacket.java @@ -0,0 +1,20 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.legacy.network.packet.PacketType; + +/** + * PING request. + */ +public class PingRequestInPacket extends MqttReadablePacket { + + public static final byte PACKET_TYPE = (byte) PacketType.PING_REQUEST.ordinal(); + + public PingRequestInPacket(byte info) { + super(info); + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingResponseInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingResponseInPacket.java new file mode 100644 index 00000000..0c49c444 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PingResponseInPacket.java @@ -0,0 +1,20 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.legacy.network.packet.PacketType; + +/** + * PING response. + */ +public class PingResponseInPacket extends MqttReadablePacket { + + public static final byte PACKET_TYPE = (byte) PacketType.PING_RESPONSE.ordinal(); + + public PingResponseInPacket(byte info) { + super(info); + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishAckInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishAckInPacket.java new file mode 100644 index 00000000..4823dc52 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishAckInPacket.java @@ -0,0 +1,97 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import lombok.Getter; + +/** + * Publish acknowledgment (QoS 1). + */ +@Getter +public class PublishAckInPacket extends MqttReadablePacket implements HasPacketId { + + private static final int PACKET_TYPE = PacketType.PUBLISH_ACK.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and is not intended to be parsed by + the receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this property if it would increase the size of the PUBACK packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the PUBACK + packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.4.2-3]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private PublishAckReasonCode reasonCode; + private int packetId; + + // properties + private String reason; + + public PublishAckInPacket(byte info) { + super(info); + this.reasonCode = PublishAckReasonCode.SUCCESS; + this.reason = ""; + } + + @Override + public byte getPacketType() { + return (byte) PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718045 + packetId = readUnsignedShort(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901123 + if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { + reasonCode = PublishAckReasonCode.of(readUnsignedByte(buffer)); + } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + return connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining(); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishCompleteInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishCompleteInPacket.java new file mode 100644 index 00000000..2a6e817e --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishCompleteInPacket.java @@ -0,0 +1,99 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import lombok.Getter; + +/** + * Publish complete (QoS 2 delivery part 3). + */ +@Getter +public class PublishCompleteInPacket extends MqttReadablePacket implements HasPacketId { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_COMPLETED.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private PublishCompletedReasonCode reasonCode; + private int packetId; + + // properties + private String reason; + + public PublishCompleteInPacket(byte info) { + super(info); + this.reasonCode = PublishCompletedReasonCode.SUCCESS; + this.reason = ""; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + super.readVariableHeader(connection, buffer); + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718083 + packetId = readUnsignedShort(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 + if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { + reasonCode = PublishCompletedReasonCode.of(readUnsignedByte(buffer)); + } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 + return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishInPacket.java new file mode 100644 index 00000000..c4884d63 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishInPacket.java @@ -0,0 +1,358 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import static javasabr.mqtt.model.utils.TopicUtils.EMPTY_TOPIC_NAME; +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.IntArray; +import javasabr.rlib.collections.array.MutableIntArray; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.NumberUtils; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Publish message. + */ +@Getter +public class PublishInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH.ordinal(); + + static { + DebugUtils.registerIncludedFields("topicName", "qos", "duplicate", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the value of the Payload Format Indicator, either of: + • 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a + Payload Format Indicator. + • 1 (0x01) Byte Indicates that the Payload is UTF-8 Encoded Character Data. The UTF-8 data in + the Payload MUST be well-formed UTF-8 as defined by the Unicode specification [Unicode] + and restated in RFC 3629 [RFC3629]. + + A Server MUST send the Payload Format Indicator unaltered to all subscribers receiving the Application + 1563 Message [MQTT-3.3.2-4]. The receiver MAY validate that the Payload is of the format indicated, and if it + is not send a PUBACK, PUBREC, or DISCONNECT with Reason Code of 0x99 (Payload format invalid) + as described in section 4.13. Refer to section 5.4.9 for information about security issues in validating the + payload format. + */ + PacketProperty.PAYLOAD_FORMAT_INDICATOR, + /* + Followed by the Four Byte Integer representing the Message Expiry Interval. + + If present, the Four Byte value is the lifetime of the Application Message in seconds. If the Message + Expiry Interval has passed and the Server has not managed to start onward delivery to a matching + subscriber, then it MUST delete the copy of the message for that subscriber [MQTT-3.3.2-5]. + + If absent, the Application Message does not expire. + + The PUBLISH packet sent to a Client by the Server MUST contain a Message Expiry Interval set to the + received value minus the time that the Application Message has been waiting in the Server [MQTT-3.3.2- + 6]. Refer to section 4.1 for details and limitations of stored state. + */ + PacketProperty.MESSAGE_EXPIRY_INTERVAL, + /* + Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to include the + Topic Alias value more than once. + + A Topic Alias is an integer value that is used to identify the Topic instead of using the Topic Name. This + reduces the size of the PUBLISH packet, and is useful when the Topic Names are long and the same + Topic Names are used repetitively within a Network Connection. + + The sender decides whether to use a Topic Alias and chooses the value. It sets a Topic Alias mapping by + including a non-zero length Topic Name and a Topic Alias in the PUBLISH packet. The receiver + processes the PUBLISH as normal but also sets the specified Topic Alias mapping to this Topic Name. + + If a Topic Alias mapping has been set at the receiver, a sender can send a PUBLISH packet that contains + that Topic Alias and a zero length Topic Name. The receiver then treats the incoming PUBLISH as if it + had contained the Topic Name of the Topic Alias. + + A sender can modify the Topic Alias mapping by sending another PUBLISH in the same Network + Connection with the same Topic Alias value and a different non-zero length Topic Name. + + Topic Alias mappings exist only within a Network Connection and last only for the lifetime of that Network + Connection. A receiver MUST NOT carry forward any Topic Alias mappings from one Network + Connection to another [MQTT-3.3.2-7]. + + A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic + Alias which has the value 0 [MQTT-3.3.2-8]. + + A Client MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum + value returned by the Server in the CONNACK packet [MQTT-3.3.2-9]. A Client MUST accept all Topic + Alias values greater than 0 and less than or equal to the Topic Alias Maximum value that it sent in the + CONNECT packet [MQTT-3.3.2-10]. + + A Server MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum + value sent by the Client in the CONNECT packet [MQTT-3.3.2-11]. A Server MUST accept all Topic Alias + values greater than 0 and less than or equal to the Topic Alias Maximum value that it returned in the + CONNACK packet [MQTT-3.3.2-12]. + + The Topic Alias mappings used by the Client and Server are independent from each other. Thus, when a + Client sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a PUBLISH + with a Topic Alias value of 1 to that Client they will in general be referring to different Topics. + */ + PacketProperty.TOPIC_ALIAS, + /* + Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The + Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The + Response Topic MUST NOT contain wildcard characters [MQTT-3.3.2-14]. It is a Protocol Error to include + the Response Topic more than once. The presence of a Response Topic identifies the Message as a + Request. + + Refer to section 4.10 for more information about Request / Response. + + The Server MUST send the Response Topic unaltered to all subscribers receiving the Application + Message [MQTT-3.3.2-15]. + */ + PacketProperty.RESPONSE_TOPIC, + /* + Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify + which request the Response Message is for when it is received. It is a Protocol Error to include + Correlation Data more than once. If the Correlation Data is not present, the Requester does not require + any correlation data. + + The Server MUST send the Correlation Data unaltered to all subscribers receiving the Application + Message [MQTT-3.3.2-16]. The value of the Correlation Data only has meaning to the sender of the + Request Message and receiver of the Response Message. + */ + PacketProperty.CORRELATION_DATA, + /* + Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent + multiple name, value pairs. The same name is allowed to appear more than once. + + The Server MUST send all User Properties unaltered in a PUBLISH packet when forwarding the + Application Message to a Client [MQTT-3.3.2-17]. The Server MUST maintain the order of User + Properties when forwarding the Application Message [MQTT-3.3.2-18]. + */ + PacketProperty.USER_PROPERTY, + /* + Followed by a Variable Byte Integer representing the identifier of the subscription. + + The Subscription Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the + Subscription Identifier has a value of 0. Multiple Subscription Identifiers will be included if the + publication + is the result of a match to more than one subscription, in this case their order is not significant. + */ + PacketProperty.SUBSCRIPTION_IDENTIFIER, + /* + Followed by a UTF-8 Encoded String describing the content of the Application Message. The Content + Type MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-19]. + It is a Protocol Error to include the Content Type more than once. The value of the Content Type is + defined by the sending and receiving application. + + A Server MUST send the Content Type unaltered to all subscribers receiving the Application Message + [MQTT-3.3.2-20]. + */ + PacketProperty.CONTENT_TYPE); + + /** + * This field indicates the level of assurance for delivery of an Application Message. The QoS levels are shown + * below. + *

+ * 0 - 00 At most once delivery 1 --01 At least once delivery 2 - 10 Exactly once delivery - - 11 Reserved – must not + * be used + *

+ * If the Server included a Maximum QoS in its CONNACK response to a Client and it receives a PUBLISH packet with a + * QoS greater than this, then it uses DISCONNECT with Reason Code 0x9B (QoS not supported) as described in section + * 4.13 Handling errors. + *

+ * A PUBLISH Packet MUST NOT have both QoS bits set to 1 [MQTT-3.3.1-4]. If a Server or Client receives a PUBLISH + * packet which has both QoS bits set to 1 it is a Malformed Packet. Use DISCONNECT with Reason Code 0x81 (Malformed + * Packet) as described in + */ + private final QoS qos; + + /** + * If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or Server has attempted + * to send this PUBLISH packet. If the DUP flag is set to 1, it indicates that this might be re-delivery of an earlier + * attempt to send the packet. + *

+ * The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH packet + * [MQTT-3.3.1-1]. The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. + *

+ * The value of the DUP flag from an incoming PUBLISH packet is not propagated when the PUBLISH packet is sent to + * subscribers by the Server. The DUP flag in the outgoing PUBLISH packet is set independently to the incoming PUBLISH + * packet, its value MUST be determined solely by whether the outgoing PUBLISH packet is a retransmission + */ + private final boolean duplicate; + + /** + * If the RETAIN flag is set to 1 in a PUBLISH packet sent by a Client to a Server, the Server MUST replace any + * existing retained message for this topic and store the Application Message [MQTT-3.3.1-5], so that it can be + * delivered to future subscribers whose subscriptions match its Topic Name. If the Payload contains zero bytes it is + * processed normally by the Server but any retained message with the same topic name MUST be removed and any future + * subscribers for the topic will not receive a retained message [MQTT1476 3.3.1-6]. A retained message with a Payload + * containing zero bytes MUST NOT be stored as a retained message on the Server [MQTT-3.3.1-7]. + *

+ * If the RETAIN flag is 0 in a PUBLISH packet sent by a Client to a Server, the Server MUST NOT store the message as + * a retained message and MUST NOT remove or replace any existing retained message [MQTT-3.3.1-8]. + *

+ * If the Server included Retain Available in its CONNACK response to a Client with its value set to 0 and it receives + * a PUBLISH packet with the RETAIN flag is set to 1, then it uses the DISCONNECT Reason Code of 0x9A (Retain not + * supported) as described in section 4.13. + *

+ * When a new Non-shared Subscription is made, the last retained message, if any, on each matching topic name is sent + * to the Client as directed by the Retain Handling Subscription Option. These messages are sent with the RETAIN flag + * set to 1. Which retained messages are sent is controlled by the Retain Handling Subscription Option. At the time of + * the Subscription: • If Retain Handling is set to 0 the Server MUST send the retained messages matching the Topic + * Filter of the subscription to the Client [MQTT-3.3.1-9]. • If Retain Handling is set to 1 then if the subscription + * did not already exist, the Server MUST send all retained message matching the Topic Filter of the subscription to + * the Client, and if the subscription did exist the Server MUST NOT send the retained messages. [MQTT-3.3.1-10]. • If + * Retain Handling is set to 2, the Server MUST NOT send the retained messages + *

+ * If the Server receives a PUBLISH packet with the RETAIN flag set to 1, and QoS 0 it SHOULD store the new QoS 0 + * message as the new retained message for that topic, but MAY choose to discard it at any time. If this happens there + * will be no retained message for that topic. + *

+ * If the current retained message for a Topic expires, it is discarded and there will be no retained message for that + * topic. + *

+ * The setting of the RETAIN flag in an Application Message forwarded by the Server from an established connection is + * controlled by the Retain As Published subscription option. Refer to section 3.8.3.1 for a definition of the + * Subscription Options. • If the value of Retain As Published subscription option is set to 0, the Server MUST set + * the RETAIN flag to 0 when forwarding an Application Message regardless of how the RETAIN flag was set in the + * received PUBLISH packet [MQTT-3.3.1-12]. • If the value of Retain As Published subscription option is set to 1, the + * Server MUST set the RETAIN flag equal to the RETAIN flag in the received PUBLISH packet + */ + private final boolean retained; + + /** + * The Topic Name identifies the information channel to which Payload data is published. + *

+ * The Topic Name MUST be present as the first field in the PUBLISH packet Variable Header. It MUST be a UTF-8 Encoded + * String as defined in section 1.5.4 [MQTT-3.3.2-1]. + *

+ * Standards Track Work Product Copyright © OASIS Open 2019. All Rights Reserved. Page 56 of 137 The Topic Name in the + * PUBLISH packet MUST NOT contain wildcard characters [MQTT-3.3.2-2]. + *

+ * The Topic Name in a PUBLISH packet sent by a Server to a subscribing Client MUST match the Subscription’s Topic + * Filter according to the matching process defined in section 4.7 [MQTT-3.3.2-3]. However, as the Server is permitted + * to map the Topic Name to another name, it might not be the same as the Topic Name in the original PUBLISH packet. + *

+ * To reduce the size of the PUBLISH packet the sender can use a Topic Alias. The Topic Alias is described in section + * 3.3.2.3.4. It is a Protocol Error if the Topic Name is zero length and there is no Topic Alias. + */ + private TopicName topicName; + + /** + * The Packet Identifier field is only present in PUBLISH packets where the QoS level is 1 or 2. Section 2.2.1 + * provides more information about Packet Identifiers. + */ + private int packetId; + + private byte[] payload; + + // properties + private String responseTopic; + private String contentType; + + private IntArray subscriptionIds; + + private byte[] correlationData; + + private long messageExpiryInterval = MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED; + private int topicAlias = MqttProperties.TOPIC_ALIAS_DEFAULT; + private boolean payloadFormatIndicator = MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT; + + public PublishInPacket(byte info) { + super(info); + this.qos = QoS.of((info >> 1) & 0x03); + this.retained = NumberUtils.isSetBit(info, 0); + this.duplicate = NumberUtils.isSetBit(info, 3); + this.topicName = EMPTY_TOPIC_NAME; + this.responseTopic = StringUtils.EMPTY; + this.contentType = StringUtils.EMPTY; + this.correlationData = ArrayUtils.EMPTY_BYTE_ARRAY; + this.payload = ArrayUtils.EMPTY_BYTE_ARRAY; + this.subscriptionIds = IntArray.empty(); + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718039 + topicName = buildTopicName(readString(buffer)); + packetId = qos != QoS.AT_MOST_ONCE ? readUnsignedShort(buffer) : 0; + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718040 + payload = readPayload(buffer); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, long value) { + switch (property) { + case PAYLOAD_FORMAT_INDICATOR: + payloadFormatIndicator = NumberUtils.toBoolean(value); + break; + case TOPIC_ALIAS: + topicAlias = NumberUtils.validate( + (int) value, + MqttProperties.TOPIC_ALIAS_MIN, + MqttProperties.TOPIC_ALIAS_MAX); + break; + case MESSAGE_EXPIRY_INTERVAL: + messageExpiryInterval = value; + break; + case SUBSCRIPTION_IDENTIFIER: + if (subscriptionIds == IntArray.empty()) { + subscriptionIds = ArrayFactory.mutableIntArray(); + } + if (subscriptionIds instanceof MutableIntArray array) { + array.add((int) value); + } + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case RESPONSE_TOPIC: + // TODO should be validated + responseTopic = value; + break; + case CONTENT_TYPE: + contentType = value; + break; + default: + unexpectedProperty(property); + } + } + + @Override + protected void applyProperty(PacketProperty property, byte[] value) { + switch (property) { + case CORRELATION_DATA: + correlationData = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReceivedInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReceivedInPacket.java new file mode 100644 index 00000000..42d3aadc --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReceivedInPacket.java @@ -0,0 +1,100 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Publish received (QoS 2 delivery part 1). + */ +@Getter +public class PublishReceivedInPacket extends MqttReadablePacket implements HasPacketId { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RECEIVED.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private PublishReceivedReasonCode reasonCode; + private int packetId; + + // properties + private String reason; + + public PublishReceivedInPacket(byte info) { + super(info); + this.reasonCode = PublishReceivedReasonCode.SUCCESS; + this.reason = StringUtils.EMPTY; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + super.readVariableHeader(connection, buffer); + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718050 + packetId = readUnsignedShort(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 + if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { + reasonCode = PublishReceivedReasonCode.of(readUnsignedByte(buffer)); + } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901144 + return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReleaseInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReleaseInPacket.java new file mode 100644 index 00000000..e9feccd9 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/PublishReleaseInPacket.java @@ -0,0 +1,99 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import lombok.Getter; + +/** + * Publish release (QoS 2 delivery part 2). + */ +@Getter +public class PublishReleaseInPacket extends MqttReadablePacket implements HasPacketId { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RELEASED.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private PublishReleaseReasonCode reasonCode; + private int packetId; + + // properties + private String reason; + + public PublishReleaseInPacket(byte info) { + super(info); + this.reasonCode = PublishReleaseReasonCode.SUCCESS; + this.reason = ""; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + super.readVariableHeader(connection, buffer); + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718055 + packetId = readUnsignedShort(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 + if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { + reasonCode = PublishReleaseReasonCode.of(readUnsignedByte(buffer)); + } + } + + @Override + protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 + return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeAckInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeAckInPacket.java new file mode 100644 index 00000000..04e11136 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeAckInPacket.java @@ -0,0 +1,94 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Subscribe acknowledgement. + */ +@Getter +public class SubscribeAckInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE_ACK.ordinal(); + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the + Client. + + The Server uses this value to give additional information to the Client. The Server MUST NOT send this + Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified + by the Client + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet + beyond the Maximum Packet Size specified by Client [MQTT-3.9.2-2]. The User Property is allowed to + appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more + than once. + */ + PacketProperty.USER_PROPERTY); + + private MutableArray reasonCodes; + private int packetId; + + // properties + private String reason; + + public SubscribeAckInPacket(byte info) { + super(info); + this.reasonCodes = ArrayFactory.mutableArray(SubscribeAckReasonCode.class); + this.reason = StringUtils.EMPTY; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718070 + packetId = readUnsignedShort(buffer); + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718071 + if (buffer.remaining() < 1) { + throw new IllegalStateException("No any topic filters."); + } + + while (buffer.hasRemaining()) { + reasonCodes.add(SubscribeAckReasonCode.of(readUnsignedByte(buffer))); + } + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeInPacket.java new file mode 100644 index 00000000..b2b633c3 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/SubscribeInPacket.java @@ -0,0 +1,120 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.SubscribeRetainHandling; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.NumberUtils; +import lombok.Getter; + +/** + * Subscribe request. + */ +@Getter +public class SubscribeInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE.ordinal(); + + static { + DebugUtils.registerIncludedFields("packetId", "topicFilters"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription + Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a + value of 0. It is a Protocol Error to include the Subscription Identifier more than once. + + The Subscription Identifier is associated with any subscription created or modified as the result of this + SUBSCRIBE packet. If there is a Subscription Identifier, it is stored with the subscription. If this + property is + not specified, then the absence of a Subscription Identifier is stored with the subscription. + */ + PacketProperty.SUBSCRIPTION_IDENTIFIER, + /* + The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same + name is allowed to appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private MutableArray topicFilters; + private int packetId; + + // properties + private int subscriptionId; + + public SubscribeInPacket(byte info) { + super(info); + this.topicFilters = ArrayFactory.mutableArray(SubscribeTopicFilter.class); + this.subscriptionId = MqttProperties.SUBSCRIPTION_ID_UNDEFINED; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 + packetId = readUnsignedShort(buffer); + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + + if (buffer.remaining() < 1) { + throw new IllegalStateException("No any topic filters."); + } + + boolean isMqtt5 = connection.isSupported(MqttVersion.MQTT_5); + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901168 + while (buffer.hasRemaining()) { + + var topicFilter = readString(buffer); + var options = readUnsignedByte(buffer); + + var qos = QoS.of(options & 0x03); + var retainHandling = isMqtt5 ? SubscribeRetainHandling.of((options >> 4) & 0x03) : SubscribeRetainHandling.SEND; + + if (qos == QoS.INVALID || retainHandling == SubscribeRetainHandling.INVALID) { + throw new IllegalStateException("Unsupported qos or retain handling"); + } + + var noLocal = !isMqtt5 || NumberUtils.isSetBit(options, 2); + var rap = !isMqtt5 || NumberUtils.isSetBit(options, 3); + + topicFilters.add(new SubscribeTopicFilter(buildTopicFilter(topicFilter), qos, retainHandling, noLocal, rap)); + } + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, long value) { + switch (property) { + case SUBSCRIPTION_IDENTIFIER: + subscriptionId = (int) value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeAckInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeAckInPacket.java new file mode 100644 index 00000000..f3ab147e --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeAckInPacket.java @@ -0,0 +1,102 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.StringUtils; +import lombok.Getter; + +/** + * Unsubscribe acknowledgement. + */ +@Getter +public class UnsubscribeAckInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE_ACK.ordinal(); + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the + Client. + + The Server uses this value to give additional information to the Client. The Server MUST NOT send this + Property if it would increase the size of the UNSUBACK packet beyond the Maximum Packet Size + specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK + packet beyond the Maximum Packet Size specified by the Client [MQTT-3.11.2-2]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private MutableArray reasonCodes; + private int packetId; + + // properties + private String reason; + + public UnsubscribeAckInPacket(byte info) { + super(info); + this.reasonCodes = ArrayFactory.mutableArray(UnsubscribeAckReasonCode.class); + this.reason = StringUtils.EMPTY; + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718079 + packetId = readUnsignedShort(buffer); + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901194 + if (!connection.isSupported(MqttVersion.MQTT_5)) { + return; + } + + if (!buffer.hasRemaining()) { + return; + } + + reasonCodes = ArrayFactory.mutableArray(UnsubscribeAckReasonCode.class, buffer.remaining()); + + while (buffer.hasRemaining()) { + reasonCodes.add(UnsubscribeAckReasonCode.of(readUnsignedByte(buffer))); + } + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } + + @Override + protected void applyProperty(PacketProperty property, String value) { + switch (property) { + case REASON_STRING: + reason = value; + break; + default: + unexpectedProperty(property); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeInPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeInPacket.java new file mode 100644 index 00000000..b3d415c9 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/UnsubscribeInPacket.java @@ -0,0 +1,65 @@ +package javasabr.mqtt.legacy.network.packet.in; + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.legacy.network.MqttConnection; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.ArrayFactory; +import javasabr.rlib.collections.array.MutableArray; +import lombok.Getter; + +/** + * Unsubscribe request. + */ +@Getter +public class UnsubscribeInPacket extends MqttReadablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE.ordinal(); + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same + name is allowed to appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private MutableArray topicFilters; + private int packetId; + + public UnsubscribeInPacket(byte info) { + super(info); + this.topicFilters = ArrayFactory.mutableArray(TopicFilter.class); + } + + @Override + public byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { + packetId = readUnsignedShort(buffer); + } + + @Override + protected void readPayload(MqttConnection connection, ByteBuffer buffer) { + + if (buffer.remaining() < 1) { + throw new IllegalStateException("No any topic filters."); + } + + while (buffer.hasRemaining()) { + topicFilters.add(buildTopicFilter(readString(buffer))); + } + } + + @Override + protected Set getAvailableProperties() { + return AVAILABLE_PROPERTIES; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/package-info.java new file mode 100644 index 00000000..56bbd2f0 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/in/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.network.packet.in; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Authentication5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Authentication5OutPacket.java new file mode 100644 index 00000000..01add7b7 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Authentication5OutPacket.java @@ -0,0 +1,86 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; +import lombok.RequiredArgsConstructor; + +/** + * Authentication exchange. + */ +@RequiredArgsConstructor +public class Authentication5OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.AUTHENTICATE.ordinal(); + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol + Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more + information about extended authentication. + */ + PacketProperty.AUTHENTICATION_METHOD, + /* + Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication + Data more than once. The contents of this data are defined by the authentication method. Refer to + section 4.12 for more information about extended authentication. + */ + PacketProperty.AUTHENTICATION_DATA, + /* + Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is + human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. + + The sender MUST NOT send this property if it would increase the size of the AUTH packet beyond the + Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the + Reason String more than once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the AUTH packet + beyond the Maximum Packet Size specified by the receiver [MQTT-3.15.2-3]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + + private final AuthenticateReasonCode reasonCode; + + private final String reason; + private final String authenticateMethod; + + private final byte[] authenticateData; + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901219 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901221 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticateMethod); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticateData); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect311OutPacket.java new file mode 100644 index 00000000..1f53848f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect311OutPacket.java @@ -0,0 +1,117 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.StringUtils; +import lombok.RequiredArgsConstructor; + +/** + * Connect request. + */ +@RequiredArgsConstructor +public class Connect311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.CONNECT.ordinal(); + + private final String username; + private final String willTopic; + private final String clientId; + + private final byte[] password; + private final byte[] willPayload; + + private final QoS willQos; + + private final int keepAlive; + + private final boolean willRetain; + private final boolean cleanStart; + + public Connect311OutPacket(String clientId, int keepAlive) { + this( + StringUtils.EMPTY, + StringUtils.EMPTY, + clientId, + ArrayUtils.EMPTY_BYTE_ARRAY, + ArrayUtils.EMPTY_BYTE_ARRAY, + QoS.AT_MOST_ONCE, + keepAlive, + false, + false); + } + + protected MqttVersion getMqttVersion() { + return MqttVersion.MQTT_3_1_1; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + + var mqttVersion = getMqttVersion(); + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030 + writeString(buffer, mqttVersion.getName()); + writeByte(buffer, mqttVersion.getVersion()); + writeByte(buffer, buildConnectFlags()); + writeShort(buffer, keepAlive); + } + + @Override + protected void writePayload(ByteBuffer buffer) { + + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031 + writeString(buffer, clientId); + + if (StringUtils.isNotEmpty(willTopic)) { + appendWillProperties(buffer); + writeString(buffer, willTopic); + writeBytes(buffer, willPayload); + } + + if (StringUtils.isNotEmpty(username)) { + writeString(buffer, username); + } + + if (ArrayUtils.isNotEmpty(password)) { + writeBytes(buffer, password); + } + } + + private int buildConnectFlags() { + + int connectFlags = 0; + + if (StringUtils.isNotEmpty(username)) { + connectFlags |= 0b1000_0000; + } + + if (ArrayUtils.isNotEmpty(password)) { + connectFlags |= 0b0100_0000; + } + + if (StringUtils.isNotEmpty(willTopic)) { + connectFlags |= 0b0000_0100; + connectFlags |= (willQos.ordinal() << 3); + if (willRetain) { + connectFlags |= 0b0010_0000; + } + } + + if (cleanStart) { + connectFlags |= 0b0000_0010; + } + + return connectFlags; + } + + protected void appendWillProperties(ByteBuffer buffer) { + } +} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Connect5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect5OutPacket.java similarity index 61% rename from src/main/java/com/ss/mqtt/broker/network/packet/out/Connect5OutPacket.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect5OutPacket.java index eabaf415..006cecd5 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Connect5OutPacket.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Connect5OutPacket.java @@ -1,12 +1,11 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.util.MqttDataUtils; - +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.MqttVersion; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.legacy.util.MqttDataUtils; import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; @@ -19,7 +18,7 @@ */ public class Connect5OutPacket extends Connect311OutPacket { - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* If the Session Expiry Interval is absent the value 0 is used. If it is set to 0, or is absent, the Session ends when the Network Connection is closed. @@ -28,7 +27,7 @@ public class Connect5OutPacket extends Connect311OutPacket { The Client and Server MUST store the Session State after the Network Connection is closed if the Session Expiry Interval is greater than 0 */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + PacketProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -39,7 +38,7 @@ public class Connect5OutPacket extends Connect311OutPacket { The value of Receive Maximum applies only to the current Network Connection. If the Receive Maximum value is absent then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM, + PacketProperty.RECEIVE_MAXIMUM, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Client is willing to accept. If the Maximum Packet Size is not present, no limit on the packet size is imposed beyond the limitations in @@ -48,7 +47,7 @@ public class Connect5OutPacket extends Connect311OutPacket { It is a Protocol Error to include the Maximum Packet Size more than once, or for the value to be set to zero. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + PacketProperty.MAXIMUM_PACKET_SIZE, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -61,7 +60,7 @@ public class Connect5OutPacket extends Connect311OutPacket { this connection. If Topic Alias Maximum is absent or zero, the Server MUST NOT send any Topic Aliases to the Client [MQTT-3.1.2-27]. */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + PacketProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is Protocol Error to include the Request Response Information more than once, or to have a value other than 0 or 1. If the Request Response Information is @@ -71,7 +70,7 @@ public class Connect5OutPacket extends Connect311OutPacket { value of 0 indicates that the Server MUST NOT return Response Information [MQTT-3.1.2-28]. If the value is 1 the Server MAY return Response Information in the CONNACK packet. */ - PacketProperty.REQUEST_RESPONSE_INFORMATION, + PacketProperty.REQUEST_RESPONSE_INFORMATION, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Request Problem Information more than once, or to have a value other than 0 or 1. If the Request Problem Information is @@ -90,12 +89,12 @@ public class Connect5OutPacket extends Connect311OutPacket { If this value is 1, the Server MAY return a Reason String or User Properties on any packet where it is allowed. */ - PacketProperty.REQUEST_PROBLEM_INFORMATION, + PacketProperty.REQUEST_PROBLEM_INFORMATION, /* The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once */ - PacketProperty.USER_PROPERTY, + PacketProperty.USER_PROPERTY, /* Followed by a UTF-8 Encoded String containing the name of the authentication method used for extended authentication .It is a Protocol Error to include Authentication Method more than once. @@ -104,7 +103,7 @@ public class Connect5OutPacket extends Connect311OutPacket { If a Client sets an Authentication Method in the CONNECT, the Client MUST NOT send any packets other than AUTH or DISCONNECT packets until it has received a CONNACK packet */ - PacketProperty.AUTHENTICATION_METHOD, + PacketProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication Data if there is no Authentication Method. It is a Protocol Error to include Authentication Data more than @@ -113,10 +112,9 @@ public class Connect5OutPacket extends Connect311OutPacket { The contents of this data are defined by the authentication method. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA - ); + PacketProperty.AUTHENTICATION_DATA); - private static final Set WILL_PROPERTIES = EnumSet.of( + private static final Set WILL_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Will Delay Interval in seconds. It is a Protocol Error to include the Will Delay Interval more than once. If the Will Delay Interval is absent, the default value is 0 @@ -126,7 +124,7 @@ public class Connect5OutPacket extends Connect311OutPacket { Session ends, whichever happens first. If a new Network Connection to this Session is made before the Will Delay Interval has passed, the Server MUST NOT send the Will Message */ - PacketProperty.WILL_DELAY_INTERVAL, + PacketProperty.WILL_DELAY_INTERVAL, /* Followed by the value of the Payload Format Indicator, either of: • 0 (0x00) Byte Indicates that the Will Message is unspecified bytes, which is equivalent to not @@ -138,7 +136,7 @@ public class Connect5OutPacket extends Connect311OutPacket { It is a Protocol Error to include the Payload Format Indicator more than once. The Server MAY validate that the Will Message is of the format indicated, and if it is not se */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + PacketProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. It is a Protocol Error to include the Message Expiry Interval more than once. @@ -148,19 +146,19 @@ public class Connect5OutPacket extends Connect311OutPacket { If absent, no Message Expiry Interval is sent when the Server publishes the Will Message. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + PacketProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by a UTF-8 Encoded String describing the content of the Will Message. It is a Protocol Error to include the Content Type more than once. The value of the Content Type is defined by the sending and receiving application. */ - PacketProperty.CONTENT_TYPE, + PacketProperty.CONTENT_TYPE, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. It is a Protocol Error to include the Response Topic more than once. The presence of a Response Topic identifies the Will Message as a Request. */ - PacketProperty.RESPONSE_TOPIC, + PacketProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include @@ -170,153 +168,134 @@ public class Connect5OutPacket extends Connect311OutPacket { The value of the Correlation Data only has meaning to the sender of the Request Message and receiver of the Response Message. */ - PacketProperty.CORRELATION_DATA, + PacketProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. The Server MUST maintain the order of User Properties when publishing the Will Message */ - PacketProperty.USER_PROPERTY - ); - - // properties - private final Array userProperties; - private final String authenticationMethod; - private final byte[] authenticationData; - - private final long sessionExpiryInterval; - private final int receiveMax; - private final int maximumPacketSize; - private final int topicAliasMaximum; - private final boolean requestResponseInformation; - private final boolean requestProblemInformation; - - public Connect5OutPacket(String clientId, int keepAlive) { - this( - StringUtils.EMPTY, - StringUtils.EMPTY, - clientId, - ArrayUtils.EMPTY_BYTE_ARRAY, - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - false, - false, - Array.empty(StringPair.class), - StringUtils.EMPTY, - ArrayUtils.EMPTY_BYTE_ARRAY, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED, - MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED, - false, - false - ); - } - - public Connect5OutPacket( - String username, - String willTopic, - String clientId, - byte[] password, - byte[] willPayload, - QoS willQos, - int keepAlive, - boolean willRetain, - boolean cleanStart, - Array userProperties, - String authenticationMethod, - byte[] authenticationData, - long sessionExpiryInterval, - int receiveMax, - int maximumPacketSize, - int topicAliasMaximum, - boolean requestResponseInformation, - boolean requestProblemInformation - ) { - super(username, willTopic, clientId, password, willPayload, willQos, keepAlive, willRetain, cleanStart); - this.userProperties = userProperties; - this.authenticationMethod = authenticationMethod; - this.authenticationData = authenticationData; - this.sessionExpiryInterval = sessionExpiryInterval; - this.receiveMax = receiveMax; - this.maximumPacketSize = maximumPacketSize; - this.topicAliasMaximum = topicAliasMaximum; - this.requestResponseInformation = requestResponseInformation; - this.requestProblemInformation = requestProblemInformation; - } - - protected MqttVersion getMqttVersion() { - return MqttVersion.MQTT_5; - } - - @Override - protected boolean isPropertiesSupported() { - return true; + PacketProperty.USER_PROPERTY); + + // properties + private final Array userProperties; + private final String authenticationMethod; + private final byte[] authenticationData; + + private final long sessionExpiryInterval; + private final int receiveMax; + private final int maximumPacketSize; + private final int topicAliasMaximum; + private final boolean requestResponseInformation; + private final boolean requestProblemInformation; + + public Connect5OutPacket(String clientId, int keepAlive) { + this( + StringUtils.EMPTY, + StringUtils.EMPTY, + clientId, + ArrayUtils.EMPTY_BYTE_ARRAY, + ArrayUtils.EMPTY_BYTE_ARRAY, + QoS.AT_MOST_ONCE, + keepAlive, + false, + false, + Array.empty(StringPair.class), + StringUtils.EMPTY, + ArrayUtils.EMPTY_BYTE_ARRAY, + MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED, + MqttProperties.RECEIVE_MAXIMUM_UNDEFINED, + MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED, + MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED, + false, + false); + } + + public Connect5OutPacket( + String username, + String willTopic, + String clientId, + byte[] password, + byte[] willPayload, + QoS willQos, + int keepAlive, + boolean willRetain, + boolean cleanStart, + Array userProperties, + String authenticationMethod, + byte[] authenticationData, + long sessionExpiryInterval, + int receiveMax, + int maximumPacketSize, + int topicAliasMaximum, + boolean requestResponseInformation, + boolean requestProblemInformation) { + super(username, willTopic, clientId, password, willPayload, willQos, keepAlive, willRetain, cleanStart); + this.userProperties = userProperties; + this.authenticationMethod = authenticationMethod; + this.authenticationData = authenticationData; + this.sessionExpiryInterval = sessionExpiryInterval; + this.receiveMax = receiveMax; + this.maximumPacketSize = maximumPacketSize; + this.topicAliasMaximum = topicAliasMaximum; + this.requestResponseInformation = requestResponseInformation; + this.requestProblemInformation = requestProblemInformation; + } + + protected MqttVersion getMqttVersion() { + return MqttVersion.MQTT_5; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void appendWillProperties(ByteBuffer buffer) { + + var propertiesBuffer = getPropertiesBuffer(); + + writeWillProperties(propertiesBuffer); + + if (propertiesBuffer.position() < 1) { + buffer.put((byte) 0); + return; } - @Override - protected void appendWillProperties(ByteBuffer buffer) { - - var propertiesBuffer = getPropertiesBuffer(); - - writeWillProperties(propertiesBuffer); - - if (propertiesBuffer.position() < 1) { - buffer.put((byte) 0); - return; - } - - propertiesBuffer.flip(); - - MqttDataUtils.writeMbi(propertiesBuffer.limit(), buffer) - .put(propertiesBuffer); - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901046 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); - writeProperty( - buffer, - PacketProperty.REQUEST_RESPONSE_INFORMATION, - requestResponseInformation, - false - ); - writeProperty( - buffer, - PacketProperty.REQUEST_PROBLEM_INFORMATION, - requestProblemInformation, - false - ); - writeProperty( - buffer, - PacketProperty.RECEIVE_MAXIMUM, - receiveMax, - MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED - ); - writeProperty( - buffer, - PacketProperty.TOPIC_ALIAS_MAXIMUM, - topicAliasMaximum, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED - ); - writeProperty( - buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, - sessionExpiryInterval, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED - ); - writeProperty( - buffer, - PacketProperty.MAXIMUM_PACKET_SIZE, - maximumPacketSize, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED - ); - } + propertiesBuffer.flip(); + + MqttDataUtils + .writeMbi(propertiesBuffer.limit(), buffer) + .put(propertiesBuffer); + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901046 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); + writeProperty(buffer, PacketProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation, false); + writeProperty(buffer, PacketProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation, false); + writeProperty(buffer, PacketProperty.RECEIVE_MAXIMUM, receiveMax, MqttProperties.RECEIVE_MAXIMUM_UNDEFINED); + writeProperty( + buffer, + PacketProperty.TOPIC_ALIAS_MAXIMUM, + topicAliasMaximum, + MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED); + writeProperty( + buffer, + PacketProperty.SESSION_EXPIRY_INTERVAL, + sessionExpiryInterval, + MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED); + writeProperty( + buffer, + PacketProperty.MAXIMUM_PACKET_SIZE, + maximumPacketSize, + MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED); + } - protected void writeWillProperties(ByteBuffer buffer) {} + protected void writeWillProperties(ByteBuffer buffer) { + } } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck311OutPacket.java new file mode 100644 index 00000000..34412b4c --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck311OutPacket.java @@ -0,0 +1,57 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Connect acknowledgment. + */ +@RequiredArgsConstructor +public class ConnectAck311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.CONNECT_ACK.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCode", "sessionPresent"); + } + + /** + * The values the Connect Reason Code are shown below. If a well formed CONNECT packet is received by the Server, but + * the Server is unable to complete the Connection the Server MAY send a CONNACK packet containing the appropriate + * Connect Reason code from this table. If a Server sends a CONNACK packet containing a Reason code of 128 or greater + * it MUST then close the Network Connection + */ + protected final ConnectAckReasonCode reasonCode; + + /** + * The Session Present flag informs the Client whether the Server is using Session State from a previous connection + * for this ClientID. This allows the Client and Server to have a consistent view of the Session State. If the Server + * accepts a connection with Clean Start set to 1, the Server MUST set Session Present to 0 in the CONNACK packet in + * addition to setting a 0x00 (Success) Reason Code in the CONNACK packet + */ + private final boolean sessionPresent; + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035 + buffer.put((byte) (sessionPresent ? 0x01 : 0x00)); + buffer.put(getReasonCodeValue()); + } + + protected byte getReasonCodeValue() { + return reasonCode.getMqtt311(); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck5OutPacket.java similarity index 65% rename from src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck5OutPacket.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck5OutPacket.java index 6f2f4932..4e608e3d 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck5OutPacket.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/ConnectAck5OutPacket.java @@ -1,28 +1,26 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.Array; -import org.jetbrains.annotations.NotNull; - +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.base.utils.DebugUtils; import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.rlib.collections.array.Array; /** * Connect acknowledgment. */ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { - static { - DebugUtils.registerIncludedFields("reasonCode", "sessionPresent", "clientId"); - } + static { + DebugUtils.registerIncludedFields("reasonCode", "sessionPresent", "clientId"); + } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the Four Byte Integer representing the Session Expiry Interval in seconds. It is a Protocol Error to include the Session Expiry Interval more than once. @@ -30,7 +28,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { If the Session Expiry Interval is absent the value in the CONNECT Packet used. The server uses this property to inform the Client that it is using a value other than that sent by the Client in the CONNACK. */ - PacketProperty.SESSION_EXPIRY_INTERVAL, + PacketProperty.SESSION_EXPIRY_INTERVAL, /* Followed by the Two Byte Integer representing the Receive Maximum value. It is a Protocol Error to include the Receive Maximum value more than once or for it to have the value 0. @@ -41,7 +39,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { If the Receive Maximum value is absent, then its value defaults to 65,535. */ - PacketProperty.RECEIVE_MAXIMUM, + PacketProperty.RECEIVE_MAXIMUM, /* Followed by a Byte with a value of either 0 or 1. It is a Protocol Error to include Maximum QoS more than once, or to have a value other than 0 or 1. If the Maximum QoS is absent, the Client uses a Maximum @@ -62,7 +60,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { reject the connection. It SHOULD use a CONNACK packet with Reason Code 0x9B (QoS not supported) as described in section 4.13 Handling errors, and MUST close the Network Connection */ - PacketProperty.MAXIMUM_QOS, + PacketProperty.MAXIMUM_QOS, /* Followed by a Byte field. If present, this byte declares whether the Server supports retained messages. A value of 0 means that retained messages are not supported. A value of 1 means retained messages are @@ -79,7 +77,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { Server SHOULD send a DISCONNECT with Reason Code of 0x9A (Retain not supported) as described in section 4.13. */ - PacketProperty.RETAIN_AVAILABLE, + PacketProperty.RETAIN_AVAILABLE, /* Followed by a Four Byte Integer representing the Maximum Packet Size the Server is willing to accept. If the Maximum Packet Size is not present, there is no limit on the packet size imposed beyond the @@ -96,7 +94,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { a Server receives a packet whose size exceeds this limit, this is a Protocol Error, the Server uses DISCONNECT with Reason Code 0x95 (Packet too large), as described in section 4.13. */ - PacketProperty.MAXIMUM_PACKET_SIZE, + PacketProperty.MAXIMUM_PACKET_SIZE, /* Followed by the UTF-8 string which is the Assigned Client Identifier. It is a Protocol Error to include the Assigned Client Identifier more than once. @@ -108,7 +106,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { containing an Assigned Client Identifier. The Assigned Client Identifier MUST be a new Client Identifier not used by any other Session currently in the Server [ */ - PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, + PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, /* Followed by the Two Byte Integer representing the Topic Alias Maximum value. It is a Protocol Error to include the Topic Alias Maximum value more than once. If the Topic Alias Maximum property is absent, @@ -121,7 +119,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { on this connection. If Topic Alias Maximum is absent or 0, the Client MUST NOT send any Topic Aliases on to the Server */ - PacketProperty.TOPIC_ALIAS_MAXIMUM, + PacketProperty.TOPIC_ALIAS_MAXIMUM, /* Followed by the UTF-8 Encoded String representing the reason associated with this response. This Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the @@ -131,7 +129,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { property if it would increase the size of the CONNACK packet beyond the Maximum Packet Size specified by the Client [MQTT-3.2.2-19]. It is a Protocol Error to include the Reason String more than once. */ - PacketProperty.REASON_STRING, + PacketProperty.REASON_STRING, /* Followed by a UTF-8 String Pair. This property can be used to provide additional information to the Client including diagnostic information. The Server MUST NOT send this property if it would increase the size of @@ -142,7 +140,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { The content and meaning of this property is not defined by this specification. The receiver of a CONNACK containing this property MAY ignore it. */ - PacketProperty.USER_PROPERTY, + PacketProperty.USER_PROPERTY, /* Followed by a Byte field. If present, this byte declares whether the Server supports Wildcard Subscriptions. A value is 0 means that Wildcard Subscriptions are not supported. A value of 1 means @@ -160,7 +158,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { Wildcard Subscription. In this case the Server MAY send a SUBACK Control Packet with a Reason Code 0xA2 (Wildcard Subscriptions not supported). */ - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Subscription Identifiers. A value is 0 means that Subscription Identifiers are not supported. A value of 1 means @@ -172,7 +170,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { Subscription Identifiers, this is a Protocol Error. The Server uses DISCONNECT with Reason Code of 0xA1 (Subscription Identifiers not supported) as described in section 4.13. */ - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, /* Followed by a Byte field. If present, this byte declares whether the Server supports Shared Subscriptions. A value is 0 means that Shared Subscriptions are not supported. A value of 1 means Shared @@ -183,7 +181,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { Shared Subscriptions, this is a Protocol Error. The Server uses DISCONNECT with Reason Code 0x9E (Shared Subscriptions not supported) as described in section 4.13. */ - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, + PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, /* Followed by a Two Byte Integer with the Keep Alive time assigned by the Server. If the Server sends a Server Keep Alive on the CONNACK packet, the Client MUST use this value instead of the Keep Alive @@ -191,7 +189,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { the Server MUST use the Keep Alive value set by the Client on CONNECT [MQTT-3.2.2-22]. It is a Protocol Error to include the Server Keep Alive more than once. */ - PacketProperty.SERVER_KEEP_ALIVE, + PacketProperty.SERVER_KEEP_ALIVE, /* Followed by a UTF-8 Encoded String which is used as the basis for creating a Response Topic. The way in which the Client creates a Response Topic from the Response Information is not defined by this @@ -200,7 +198,7 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { If the Client sends a Request Response Information with a value 1, it is OPTIONAL for the Server to send the Response Information in the CONNACK. */ - PacketProperty.RESPONSE_INFORMATION, + PacketProperty.RESPONSE_INFORMATION, /* Followed by a UTF-8 Encoded String which can be used by the Client to identify another Server to use. It is a Protocol Error to include the Server Reference more than once. @@ -210,187 +208,162 @@ public class ConnectAck5OutPacket extends ConnectAck311OutPacket { Refer to section 4.11 Server redirection for information about how Server Reference is used */ - PacketProperty.SERVER_REFERENCE, + PacketProperty.SERVER_REFERENCE, /* Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol Error to include the Authentication Method more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_METHOD, + PacketProperty.AUTHENTICATION_METHOD, /* Followed by Binary Data containing authentication data. The contents of this data are defined by the authentication method and the state of already exchanged authentication data. It is a Protocol Error to include the Authentication Data more than once. Refer to section 4.12 for more information about extended authentication. */ - PacketProperty.AUTHENTICATION_DATA - ); - - private final Array userProperties; - - private final String clientId; - private final String requestedClientId; - private final String reason; - private final String serverReference; - private final String responseInformation; - private final String authenticationMethod; - private final byte[] authenticationData; - private final QoS maxQos; - - private final long requestedSessionExpiryInterval; - private final long sessionExpiryInterval; - - private final int requestedKeepAlive; - private final int requestedReceiveMax; - private final int maximumPacketSize; - private final int receiveMax; - private final int topicAliasMaximum; - private final int keepAlive; - - private final boolean retainAvailable; - private final boolean wildcardSubscriptionAvailable; - private final boolean subscriptionIdAvailable; - private final boolean sharedSubscriptionAvailable; - - public ConnectAck5OutPacket( - ConnectAckReasonCode reasonCode, - boolean sessionPresent, - String requestedClientId, - long requestedSessionExpiryInterval, - int requestedKeepAlive, - int requestedReceiveMax, - String reason, - String serverReference, - String responseInformation, - String authenticationMethod, - byte[] authenticationData, - Array userProperties, - String clientId, - QoS maxQos, - long sessionExpiryInterval, - int maximumPacketSize, - int receiveMax, - int topicAliasMaximum, - int keepAlive, - boolean retainAvailable, - boolean wildcardSubscriptionAvailable, - boolean subscriptionIdAvailable, - boolean sharedSubscriptionAvailable - ) { - super(reasonCode, sessionPresent); - this.requestedClientId = requestedClientId; - this.requestedSessionExpiryInterval = requestedSessionExpiryInterval; - this.requestedKeepAlive = requestedKeepAlive; - this.requestedReceiveMax = requestedReceiveMax; - this.reason = reason; - this.serverReference = serverReference; - this.responseInformation = responseInformation; - this.authenticationMethod = authenticationMethod; - this.authenticationData = authenticationData; - this.userProperties = userProperties; - this.clientId = clientId; - this.maxQos = maxQos; - this.sessionExpiryInterval = sessionExpiryInterval; - this.maximumPacketSize = maximumPacketSize; - this.receiveMax = receiveMax; - this.topicAliasMaximum = topicAliasMaximum; - this.keepAlive = keepAlive; - this.retainAvailable = retainAvailable; - this.wildcardSubscriptionAvailable = wildcardSubscriptionAvailable; - this.subscriptionIdAvailable = subscriptionIdAvailable; - this.sharedSubscriptionAvailable = sharedSubscriptionAvailable; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected byte getReasonCodeValue() { - return reasonCode.getMqtt5(); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901080 - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_INFORMATION, responseInformation); - writeNotEmptyProperty(buffer, PacketProperty.SERVER_REFERENCE, serverReference); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); - writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeProperty( - buffer, - PacketProperty.MAXIMUM_QOS, - maxQos.ordinal(), - MqttPropertyConstants.MAXIMUM_QOS_DEFAULT.ordinal() - ); - writeProperty( - buffer, - PacketProperty.RETAIN_AVAILABLE, - retainAvailable, - MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT - ); - writeProperty( - buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, - sessionExpiryInterval, - requestedSessionExpiryInterval - ); - writeProperty( - buffer, - PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, - clientId, - requestedClientId - ); - writeProperty( - buffer, - PacketProperty.RECEIVE_MAXIMUM, - receiveMax, - requestedReceiveMax - ); - writeProperty( - buffer, - PacketProperty.MAXIMUM_PACKET_SIZE, - maximumPacketSize, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_MAX - ); - writeProperty( - buffer, - PacketProperty.TOPIC_ALIAS_MAXIMUM, - topicAliasMaximum, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_DISABLED - ); - writeProperty( - buffer, - PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, - wildcardSubscriptionAvailable, - MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - ); - writeProperty( - buffer, - PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, - subscriptionIdAvailable, - MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - ); - writeProperty( - buffer, - PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, - sharedSubscriptionAvailable, - MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - ); - writeProperty( - buffer, - PacketProperty.SERVER_KEEP_ALIVE, - keepAlive, - requestedKeepAlive - ); - } + PacketProperty.AUTHENTICATION_DATA); + + private final Array userProperties; + + private final String clientId; + private final String requestedClientId; + private final String reason; + private final String serverReference; + private final String responseInformation; + private final String authenticationMethod; + private final byte[] authenticationData; + private final QoS maxQos; + + private final long requestedSessionExpiryInterval; + private final long sessionExpiryInterval; + + private final int requestedKeepAlive; + private final int requestedReceiveMax; + private final int maximumPacketSize; + private final int receiveMax; + private final int topicAliasMaximum; + private final int keepAlive; + + private final boolean retainAvailable; + private final boolean wildcardSubscriptionAvailable; + private final boolean subscriptionIdAvailable; + private final boolean sharedSubscriptionAvailable; + + public ConnectAck5OutPacket( + ConnectAckReasonCode reasonCode, + boolean sessionPresent, + String requestedClientId, + long requestedSessionExpiryInterval, + int requestedKeepAlive, + int requestedReceiveMax, + String reason, + String serverReference, + String responseInformation, + String authenticationMethod, + byte[] authenticationData, + Array userProperties, + String clientId, + QoS maxQos, + long sessionExpiryInterval, + int maximumPacketSize, + int receiveMax, + int topicAliasMaximum, + int keepAlive, + boolean retainAvailable, + boolean wildcardSubscriptionAvailable, + boolean subscriptionIdAvailable, + boolean sharedSubscriptionAvailable) { + super(reasonCode, sessionPresent); + this.requestedClientId = requestedClientId; + this.requestedSessionExpiryInterval = requestedSessionExpiryInterval; + this.requestedKeepAlive = requestedKeepAlive; + this.requestedReceiveMax = requestedReceiveMax; + this.reason = reason; + this.serverReference = serverReference; + this.responseInformation = responseInformation; + this.authenticationMethod = authenticationMethod; + this.authenticationData = authenticationData; + this.userProperties = userProperties; + this.clientId = clientId; + this.maxQos = maxQos; + this.sessionExpiryInterval = sessionExpiryInterval; + this.maximumPacketSize = maximumPacketSize; + this.receiveMax = receiveMax; + this.topicAliasMaximum = topicAliasMaximum; + this.keepAlive = keepAlive; + this.retainAvailable = retainAvailable; + this.wildcardSubscriptionAvailable = wildcardSubscriptionAvailable; + this.subscriptionIdAvailable = subscriptionIdAvailable; + this.sharedSubscriptionAvailable = sharedSubscriptionAvailable; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected byte getReasonCodeValue() { + return reasonCode.getMqtt5(); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901080 + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_INFORMATION, responseInformation); + writeNotEmptyProperty(buffer, PacketProperty.SERVER_REFERENCE, serverReference); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_METHOD, authenticationMethod); + writeNotEmptyProperty(buffer, PacketProperty.AUTHENTICATION_DATA, authenticationData); + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeProperty( + buffer, + PacketProperty.MAXIMUM_QOS, + maxQos.ordinal(), + MqttProperties.MAXIMUM_QOS_DEFAULT.ordinal()); + writeProperty( + buffer, + PacketProperty.RETAIN_AVAILABLE, + retainAvailable, + MqttProperties.RETAIN_AVAILABLE_DEFAULT); + writeProperty( + buffer, + PacketProperty.SESSION_EXPIRY_INTERVAL, + sessionExpiryInterval, + requestedSessionExpiryInterval); + writeProperty(buffer, PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, clientId, requestedClientId); + writeProperty(buffer, PacketProperty.RECEIVE_MAXIMUM, receiveMax, requestedReceiveMax); + writeProperty( + buffer, + PacketProperty.MAXIMUM_PACKET_SIZE, + maximumPacketSize, + MqttProperties.MAXIMUM_PACKET_SIZE_MAX); + writeProperty( + buffer, + PacketProperty.TOPIC_ALIAS_MAXIMUM, + topicAliasMaximum, + MqttProperties.TOPIC_ALIAS_MAXIMUM_DISABLED); + writeProperty( + buffer, + PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, + wildcardSubscriptionAvailable, + MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT); + writeProperty( + buffer, + PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, + subscriptionIdAvailable, + MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT); + writeProperty( + buffer, + PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, + sharedSubscriptionAvailable, + MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT); + writeProperty(buffer, PacketProperty.SERVER_KEEP_ALIVE, keepAlive, requestedKeepAlive); + } } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect311OutPacket.java new file mode 100644 index 00000000..868adf16 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect311OutPacket.java @@ -0,0 +1,21 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; + +/** + * Disconnect notification. + */ +public class Disconnect311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.DISCONNECT.ordinal(); + + @Override + public int getExpectedLength() { + return 0; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect5OutPacket.java new file mode 100644 index 00000000..c610543b --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Disconnect5OutPacket.java @@ -0,0 +1,91 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; +import lombok.RequiredArgsConstructor; + +/** + * Disconnect notification. + */ +@RequiredArgsConstructor +public class Disconnect5OutPacket extends Disconnect311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. + + The Session Expiry Interval MUST NOT be sent on a DISCONNECT by the Server [MQTT-3.14.2-2]. + + If the Session Expiry Interval in the CONNECT packet was zero, then it is a Protocol Error to set a non + zero Session Expiry Interval in the DISCONNECT packet sent by the Client. If such a non-zero Session + Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server + uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in + */ + PacketProperty.SESSION_EXPIRY_INTERVAL, + /* + The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet + beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to + include the Reason String more than once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT + packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-4]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY, + /* + The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another + 2601 server)} or 0x9D (Server moved) as described in section 4.13. + */ + PacketProperty.SERVER_REFERENCE); + + private final DisconnectReasonCode reasonCode; + private final Array userProperties; + + private final String reason; + private final String serverReference; + + private final long sessionExpiryInterval; + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901209 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + writeNotEmptyProperty(buffer, PacketProperty.SERVER_REFERENCE, serverReference); + + if (sessionExpiryInterval != MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED) { + writeProperty( + buffer, + PacketProperty.SESSION_EXPIRY_INTERVAL, + sessionExpiryInterval, + MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/MqttWritablePacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/MqttWritablePacket.java new file mode 100644 index 00000000..9d8cecb8 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/MqttWritablePacket.java @@ -0,0 +1,208 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.base.utils.DebugUtils; +import javasabr.mqtt.legacy.util.MqttDataUtils; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.common.util.NumberUtils; +import javasabr.rlib.network.packet.impl.AbstractWritablePacket; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class MqttWritablePacket extends AbstractWritablePacket { + + private static final ThreadLocal LOCAL_BUFFER = ThreadLocal.withInitial(() -> ByteBuffer.allocate( + 1024 * 1024)); + + @Override + protected void writeImpl(ByteBuffer buffer) { + writeVariableHeader(buffer); + + if (isPropertiesSupported()) { + appendProperties(buffer); + } + + writePayload(buffer); + } + + protected void writeVariableHeader(ByteBuffer buffer) { + } + + protected void writePayload(ByteBuffer buffer) { + } + + protected boolean isPropertiesSupported() { + return false; + } + + protected void writeProperties(ByteBuffer buffer) { + } + + public final int getPacketTypeAndFlags() { + + var type = getPacketType(); + var controlFlags = getPacketFlags(); + + return NumberUtils.setHighByteBits(controlFlags, type); + } + + protected byte getPacketType() { + throw new UnsupportedOperationException(); + } + + protected byte getPacketFlags() { + return 0; + } + + protected ByteBuffer getPropertiesBuffer() { + return LOCAL_BUFFER.get().clear(); + } + + private void appendProperties(ByteBuffer buffer) { + + var propertiesBuffer = getPropertiesBuffer(); + + writeProperties(propertiesBuffer); + + if (propertiesBuffer.position() < 1) { + buffer.put((byte) 0); + return; + } + + propertiesBuffer.flip(); + + MqttDataUtils + .writeMbi(propertiesBuffer.limit(), buffer) + .put(propertiesBuffer); + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, boolean value) { + writeProperty(buffer, property, value ? 1 : 0); + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, boolean value, boolean def) { + if (value != def) { + writeProperty(buffer, property, value ? 1 : 0); + } + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, long value, long def) { + if (value != def) { + writeProperty(buffer, property, value); + } + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, long value) { + + buffer.put(property.getId()); + + switch (property.getDataType()) { + case BYTE: + writeByte(buffer, (int) value); + break; + case SHORT: + writeShort(buffer, (int) value); + break; + case INTEGER: + writeInt(buffer, (int) value); + break; + case MULTI_BYTE_INTEGER: + writeMbi(buffer, (int) value); + break; + default: + throw new IllegalArgumentException("Incorrect property type: " + property); + } + } + + public void writeProperty( + ByteBuffer buffer, + PacketProperty property, + String value, + String def) { + + if (!def.equals(value)) { + writeProperty(buffer, property, value); + } + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, StringPair value) { + + buffer.put(property.getId()); + writeString(buffer, value.getName()); + writeString(buffer, value.getValue()); + } + + public void writeNotEmptyProperty( + ByteBuffer buffer, + PacketProperty property, + String value) { + + if (!value.isEmpty()) { + writeProperty(buffer, property, value); + } + } + + public void writeNotEmptyProperty( + ByteBuffer buffer, + PacketProperty property, + byte[] value) { + + if (value.length > 0) { + writeProperty(buffer, property, value); + } + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, String value) { + buffer.put(property.getId()); + writeString(buffer, value); + } + + public void writeProperty(ByteBuffer buffer, PacketProperty property, byte[] value) { + buffer.put(property.getId()); + writeBytes(buffer, value); + } + + public void writeStringPairProperties( + ByteBuffer buffer, + PacketProperty property, + Array pairs) { + + if (pairs.isEmpty()) { + return; + } + + for (var pair : pairs) { + buffer.put(property.getId()); + writeStringPair(buffer, pair); + } + } + + @Override + public void writeString(ByteBuffer buffer, String string) { + var bytes = string.getBytes(StandardCharsets.UTF_8); + buffer.putShort((short) bytes.length); + buffer.put(bytes); + } + + public void writeStringPair(ByteBuffer buffer, StringPair pair) { + writeString(buffer, pair.getName()); + writeString(buffer, pair.getValue()); + } + + public void writeMbi(ByteBuffer buffer, int value) { + MqttDataUtils.writeMbi(value, buffer); + } + + public void writeBytes(ByteBuffer buffer, byte[] bytes) { + buffer.putShort((short) bytes.length); + buffer.put(bytes); + } + + @Override + public String toString() { + return DebugUtils.toJsonString(this); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingRequest311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingRequest311OutPacket.java new file mode 100644 index 00000000..7c1ac762 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingRequest311OutPacket.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; + +/** + * PING request. + */ +public class PingRequest311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PING_REQUEST.ordinal(); + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingResponse311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingResponse311OutPacket.java new file mode 100644 index 00000000..83d1efdd --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PingResponse311OutPacket.java @@ -0,0 +1,16 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; + +/** + * PING response. + */ +public class PingResponse311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PING_RESPONSE.ordinal(); + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish311OutPacket.java new file mode 100644 index 00000000..c6fc6760 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish311OutPacket.java @@ -0,0 +1,70 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; + +public class Publish311OutPacket extends PublishOutPacket { + + static { + DebugUtils.registerIncludedFields("qos", "topicName", "duplicate"); + } + + private final QoS qos; + private final byte[] payload; + private final String topicName; + + private final boolean retained; + private final boolean duplicate; + + public Publish311OutPacket( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + byte[] payload) { + super(packetId); + this.qos = qos; + this.retained = retained; + this.duplicate = duplicate; + this.payload = payload; + this.topicName = topicName; + } + + @Override + public int getExpectedLength() { + return 7 + payload.length; + } + + @Override + protected byte getPacketFlags() { + + byte info = (byte) (qos.ordinal() << 1); + + if (retained) { + info |= 0x01; + } + + if (duplicate) { + info |= 0x08; + } + + return info; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800412 + writeString(buffer, topicName); + if (qos.ordinal() > QoS.AT_MOST_ONCE.ordinal()) { + writeShort(buffer, packetId); + } + } + + @Override + protected void writePayload(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc384800413 + buffer.put(payload); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Publish5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish5OutPacket.java similarity index 76% rename from src/main/java/com/ss/mqtt/broker/network/packet/out/Publish5OutPacket.java rename to legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish5OutPacket.java index ff0b5a94..ff769d4e 100644 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Publish5OutPacket.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Publish5OutPacket.java @@ -1,23 +1,22 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.Array; +package javasabr.mqtt.legacy.network.packet.out; +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.base.utils.DebugUtils; import java.nio.ByteBuffer; import java.util.EnumSet; import java.util.Set; +import javasabr.rlib.collections.array.Array; public class Publish5OutPacket extends Publish311OutPacket { - static { - DebugUtils.registerIncludedFields("qos", "topicName", "duplicate"); - } + static { + DebugUtils.registerIncludedFields("qos", "topicName", "duplicate"); + } - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( /* Followed by the value of the Payload Forma t Indicator, either of: · 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a @@ -31,7 +30,7 @@ public class Publish5OutPacket extends Publish311OutPacket { PUBACK, PUBREC, or DISCONNECT with Reason Code of 0x99 (Payload format invalid) as described in section 4.13. Refer to section 5.4.9 for information about security issues in validating the payload format. */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, + PacketProperty.PAYLOAD_FORMAT_INDICATOR, /* Followed by the Four Byte Integer representing the Message Expiry Interval. If present, the Four Byte value is the lifetime of the Application Message in seconds. If the Message Expiry @@ -44,7 +43,7 @@ public class Publish5OutPacket extends Publish311OutPacket { value minus the time that the Application Message has been waiting in the Server [MQTT-3.3.2-6]. Refer to section 4.1 for details and limitations of stored state. */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, + PacketProperty.MESSAGE_EXPIRY_INTERVAL, /* Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to include the Topic Alias value more than once. @@ -83,7 +82,7 @@ public class Publish5OutPacket extends Publish311OutPacket { sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a PUBLISH with a Topic Alias value of 1 to that Client they will in general be referring to different Topics. */ - PacketProperty.TOPIC_ALIAS, + PacketProperty.TOPIC_ALIAS, /* Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The Response Topic MUST NOT @@ -100,7 +99,7 @@ public class Publish5OutPacket extends Publish311OutPacket { the Topic Name of a PUBLISH. If the Request Message contains a Correlation Data, the receiver of the Request Message should also include this Correlation Data as a property in the PUBLISH packet of the Response Message. */ - PacketProperty.RESPONSE_TOPIC, + PacketProperty.RESPONSE_TOPIC, /* Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify which request the Response Message is for when it is received. It is a Protocol Error to include Correlation Data @@ -121,7 +120,7 @@ public class Publish5OutPacket extends Publish311OutPacket { Refer to section 4.10 for more information about Request / Response */ - PacketProperty.CORRELATION_DATA, + PacketProperty.CORRELATION_DATA, /* Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more than once. @@ -134,56 +133,54 @@ public class Publish5OutPacket extends Publish311OutPacket { This property is intended to provide a means of transferring application layer name-value tags whose meaning and interpretation are known only by the application programs responsible for sending and receiving them. */ - PacketProperty.USER_PROPERTY - ); - - private final String responseTopic; - private final byte[] correlationData; - private final Array userProperties; - - private final int topicAlias; - - private final boolean stringPayload; - - public Publish5OutPacket( - int packetId, - QoS qos, - boolean retained, - boolean duplicate, - String topicName, - byte[] payload, - int topicAlias, - boolean stringPayload, - String responseTopic, - byte[] correlationData, - Array userProperties - ) { - super(packetId, qos, retained, duplicate, topicName, payload); - this.topicAlias = topicAlias; - this.stringPayload = stringPayload; - this.responseTopic = responseTopic; - this.correlationData = correlationData; - this.userProperties = userProperties; - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc511988586 - writeProperty(buffer, PacketProperty.PAYLOAD_FORMAT_INDICATOR, stringPayload); - writeProperty(buffer, - PacketProperty.MESSAGE_EXPIRY_INTERVAL, - 0, - MqttPropertyConstants.MESSAGE_EXPIRY_INTERVAL_UNDEFINED - ); - writeProperty(buffer, PacketProperty.TOPIC_ALIAS, topicAlias, MqttPropertyConstants.TOPIC_ALIAS_DEFAULT); - writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_TOPIC, responseTopic); - writeNotEmptyProperty(buffer, PacketProperty.CORRELATION_DATA, correlationData); - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - } + PacketProperty.USER_PROPERTY); + + private final String responseTopic; + private final byte[] correlationData; + private final Array userProperties; + + private final int topicAlias; + + private final boolean stringPayload; + + public Publish5OutPacket( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + byte[] payload, + int topicAlias, + boolean stringPayload, + String responseTopic, + byte[] correlationData, + Array userProperties) { + super(packetId, qos, retained, duplicate, topicName, payload); + this.topicAlias = topicAlias; + this.stringPayload = stringPayload; + this.responseTopic = responseTopic; + this.correlationData = correlationData; + this.userProperties = userProperties; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc511988586 + writeProperty(buffer, PacketProperty.PAYLOAD_FORMAT_INDICATOR, stringPayload); + writeProperty( + buffer, + PacketProperty.MESSAGE_EXPIRY_INTERVAL, + 0, + MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED); + writeProperty(buffer, PacketProperty.TOPIC_ALIAS, topicAlias, MqttProperties.TOPIC_ALIAS_DEFAULT); + writeNotEmptyProperty(buffer, PacketProperty.RESPONSE_TOPIC, responseTopic); + writeNotEmptyProperty(buffer, PacketProperty.CORRELATION_DATA, correlationData); + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + } } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck311OutPacket.java new file mode 100644 index 00000000..a2fb5b98 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck311OutPacket.java @@ -0,0 +1,35 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Publish acknowledgement. + */ +@RequiredArgsConstructor +public class PublishAck311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_ACK.ordinal(); + + /** + * Packet Identifier from the PUBLISH packet that is being acknowledged. + */ + private final int packetId; + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718045 + buffer.putShort((short) packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck5OutPacket.java new file mode 100644 index 00000000..20ba1d2a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishAck5OutPacket.java @@ -0,0 +1,76 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; + +/** + * Publish acknowledgement. + */ +public class PublishAck5OutPacket extends PublishAck311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and is not intended to be parsed by + the receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this property if it would increase the size of the PUBACK packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The sender MUST NOT send this property if it would increase the size of the PUBACK + packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.4.2-3]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + private final String reason; + private final PublishAckReasonCode reasonCode; + + public PublishAck5OutPacket( + int packetId, + PublishAckReasonCode reasonCode, + Array userProperties, + String reason) { + super(packetId); + this.reasonCode = reasonCode; + this.userProperties = userProperties; + this.reason = reason; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + super.writeVariableHeader(buffer); + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901123 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901125 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete311OutPacket.java new file mode 100644 index 00000000..185714e9 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete311OutPacket.java @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Publish complete (QoS 2 delivery part 3). + */ +@RequiredArgsConstructor +public class PublishComplete311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_COMPLETED.ordinal(); + + private final int packetId; + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718083 + writeShort(buffer, packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete5OutPacket.java new file mode 100644 index 00000000..b966c56b --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishComplete5OutPacket.java @@ -0,0 +1,83 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.common.util.StringUtils; + +/** + * Publish complete (QoS 2 delivery part 3). + */ +public class PublishComplete5OutPacket extends PublishComplete311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + private final PublishCompletedReasonCode reasonCode; + private final String reason; + + public PublishComplete5OutPacket(int packetId, PublishCompletedReasonCode reasonCode) { + this(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); + } + + public PublishComplete5OutPacket( + int packetId, + PublishCompletedReasonCode reasonCode, + Array userProperties, + String reason) { + super(packetId); + this.reasonCode = reasonCode; + this.userProperties = userProperties; + this.reason = reason; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + super.writeVariableHeader(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + super.writeProperties(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901155 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishOutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishOutPacket.java new file mode 100644 index 00000000..4e0e5800 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishOutPacket.java @@ -0,0 +1,20 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.HasPacketId; +import javasabr.mqtt.legacy.network.packet.PacketType; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public abstract class PublishOutPacket extends MqttWritablePacket implements HasPacketId { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH.ordinal(); + + @Getter + protected final int packetId; + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived311OutPacket.java new file mode 100644 index 00000000..bb66d34f --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived311OutPacket.java @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Publish received (QoS 2 delivery part 1). + */ +@RequiredArgsConstructor +public class PublishReceived311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RECEIVED.ordinal(); + + private final int packetId; + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718050 + writeShort(buffer, packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived5OutPacket.java new file mode 100644 index 00000000..53bb10a6 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishReceived5OutPacket.java @@ -0,0 +1,83 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.common.util.StringUtils; + +/** + * Publish received (QoS 2 delivery part 1). + */ +public class PublishReceived5OutPacket extends PublishReceived311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + private final PublishReceivedReasonCode reasonCode; + private final String reason; + + public PublishReceived5OutPacket(int packetId, PublishReceivedReasonCode reasonCode) { + this(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); + } + + public PublishReceived5OutPacket( + int packetId, + PublishReceivedReasonCode reasonCode, + Array userProperties, + String reason) { + super(packetId); + this.reasonCode = reasonCode; + this.userProperties = userProperties; + this.reason = reason; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + super.writeVariableHeader(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + super.writeProperties(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901135 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease311OutPacket.java new file mode 100644 index 00000000..60fad3fb --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease311OutPacket.java @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Publish release (QoS 2 delivery part 2). + */ +@RequiredArgsConstructor +public class PublishRelease311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RELEASED.ordinal(); + + private final int packetId; + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected byte getPacketFlags() { + return 2; + } + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718055 + writeShort(buffer, packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease5OutPacket.java new file mode 100644 index 00000000..18924933 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/PublishRelease5OutPacket.java @@ -0,0 +1,78 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; + +/** + * Publish release (QoS 2 delivery part 2). + */ +public class PublishRelease5OutPacket extends PublishRelease311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the + receiver. + + The sender uses this value to give additional information to the receiver. The sender MUST NOT send + this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size + specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the + PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User + Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is + allowed to appear more than once + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + private final PublishReleaseReasonCode reasonCode; + private final String reason; + + public PublishRelease5OutPacket( + int packetId, + PublishReleaseReasonCode reasonCode, + Array userProperties, + String reason) { + super(packetId); + this.userProperties = userProperties; + this.reasonCode = reasonCode; + this.reason = reason; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + super.writeVariableHeader(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901144 + writeByte(buffer, reasonCode.getValue()); + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + super.writeProperties(buffer); + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901145 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe311OutPacket.java new file mode 100644 index 00000000..b19dea1c --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe311OutPacket.java @@ -0,0 +1,48 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import javasabr.rlib.collections.array.Array; +import lombok.RequiredArgsConstructor; + +/** + * Subscribe request. + */ +@RequiredArgsConstructor +public class Subscribe311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE.ordinal(); + + private final Array topicFilters; + private final int packetId; + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 + writeShort(buffer, packetId); + } + + @Override + protected void writePayload(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 + for (var topicFilter : topicFilters) { + writeString(buffer, + topicFilter + .getTopicFilter() + .toString()); + writeByte(buffer, buildSubscriptionOptions(topicFilter)); + } + } + + protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { + return topicFilter + .getQos() + .ordinal(); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe5OutPacket.java new file mode 100644 index 00000000..c1c43d90 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/Subscribe5OutPacket.java @@ -0,0 +1,91 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.MqttProperties; +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.data.type.StringPair; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; + +/** + * Subscribe request. + */ +public class Subscribe5OutPacket extends Subscribe311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription + Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a + value of 0. It is a Protocol Error to include the Subscription Identifier more than once. + + The Subscription Identifier is associated with any subscription created or modified as the result of this + SUBSCRIBE packet. If there is a Subscription Identifier, it is stored with the subscription. If this + property is + not specified, then the absence of a Subscription Identifier is stored with the subscription. + */ + PacketProperty.SUBSCRIPTION_IDENTIFIER, + /* + The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same + name is allowed to appear more than once. + */ + PacketProperty.USER_PROPERTY); + + // properties + private final Array userProperties; + private final int subscriptionId; + + public Subscribe5OutPacket(Array topicFilters, int packetId) { + this(topicFilters, packetId, Array.empty(StringPair.class), MqttProperties.SUBSCRIPTION_ID_UNDEFINED); + } + + public Subscribe5OutPacket( + Array topicFilters, + int packetId, + Array userProperties, + int subscriptionId) { + super(topicFilters, packetId); + this.userProperties = userProperties; + this.subscriptionId = subscriptionId; + } + + protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { + + var subscriptionOptions = 0; + + subscriptionOptions |= topicFilter + .getRetainHandling() + .ordinal() << 4; + + if (topicFilter.isRetainAsPublished()) { + subscriptionOptions |= 0b0000_1000; + } + + if (topicFilter.isNoLocal()) { + subscriptionOptions |= 0b0000_0100; + } + + subscriptionOptions |= topicFilter + .getQos() + .ordinal(); + + return subscriptionOptions; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901164 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeProperty( + buffer, + PacketProperty.SUBSCRIPTION_IDENTIFIER, + subscriptionId, + MqttProperties.SUBSCRIPTION_ID_UNDEFINED); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck311OutPacket.java new file mode 100644 index 00000000..8ff1df8c --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck311OutPacket.java @@ -0,0 +1,55 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.packet.PacketType; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import javasabr.rlib.collections.array.Array; +import lombok.RequiredArgsConstructor; + +/** + * Subscribe acknowledgement. + */ +@RequiredArgsConstructor +public class SubscribeAck311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE_ACK.ordinal(); + + static { + DebugUtils.registerIncludedFields("reasonCodes", "packetId"); + } + + /** + * The order of Reason Codes in the SUBACK packet MUST match the order of Topic Filters in the SUBSCRIBE packet. + */ + private final Array reasonCodes; + + /** + * The Packet Identifier from the SUBSCRIBE. + */ + private final int packetId; + + @Override + public int getExpectedLength() { + return 2 + reasonCodes.size(); + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718070 + writeShort(buffer, packetId); + } + + @Override + protected void writePayload(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718071 + for (var reasonCode : reasonCodes) { + writeByte(buffer, reasonCode.getValue()); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck5OutPacket.java new file mode 100644 index 00000000..4d489262 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/SubscribeAck5OutPacket.java @@ -0,0 +1,71 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.base.utils.DebugUtils; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; + +/** + * Subscribe acknowledgement. + */ +public class SubscribeAck5OutPacket extends SubscribeAck311OutPacket { + + static { + DebugUtils.registerIncludedFields("reasonCodes", "packetId"); + } + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the + Client. + + The Server uses this value to give additional information to the Client. The Server MUST NOT send this + Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified + by the Client + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet + beyond the Maximum Packet Size specified by Client [MQTT-3.9.2-2]. The User Property is allowed to + appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more + than once. + */ + PacketProperty.USER_PROPERTY); + + private final Array userProperties; + private final String reason; + + public SubscribeAck5OutPacket( + int packetId, + Array reasonCodes, + Array userProperties, + String reason) { + super(reasonCodes, packetId); + this.userProperties = userProperties; + this.reason = reason; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901174 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck311OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck311OutPacket.java new file mode 100644 index 00000000..c405adeb --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck311OutPacket.java @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.legacy.network.packet.PacketType; +import java.nio.ByteBuffer; +import lombok.RequiredArgsConstructor; + +/** + * Unsubscribe acknowledgement. + */ +@RequiredArgsConstructor +public class UnsubscribeAck311OutPacket extends MqttWritablePacket { + + private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE_ACK.ordinal(); + + private final int packetId; + + @Override + public int getExpectedLength() { + return 2; + } + + @Override + protected byte getPacketType() { + return PACKET_TYPE; + } + + @Override + protected void writeVariableHeader(ByteBuffer buffer) { + // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718074 + writeShort(buffer, packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck5OutPacket.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck5OutPacket.java new file mode 100644 index 00000000..ec5d4e55 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/UnsubscribeAck5OutPacket.java @@ -0,0 +1,77 @@ +package javasabr.mqtt.legacy.network.packet.out; + +import javasabr.mqtt.model.PacketProperty; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import java.nio.ByteBuffer; +import java.util.EnumSet; +import java.util.Set; +import javasabr.rlib.collections.array.Array; + +/** + * Unsubscribe acknowledgement. + */ +public class UnsubscribeAck5OutPacket extends UnsubscribeAck311OutPacket { + + private static final Set AVAILABLE_PROPERTIES = EnumSet.of( + /* + Followed by the UTF-8 Encoded String representing the reason associated with this response. This + Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the + Client. + + The Server uses this value to give additional information to the Client. The Server MUST NOT send this + Property if it would increase the size of the UNSUBACK packet beyond the Maximum Packet Size + specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than + once. + */ + PacketProperty.REASON_STRING, + /* + Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other + information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK + packet beyond the Maximum Packet Size specified by the Client [MQTT-3.11.2-2]. The User Property is + allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to + appear more than once. + */ + PacketProperty.USER_PROPERTY); + + private final Array reasonCodes; + private final Array userProperties; + private final String reason; + + public UnsubscribeAck5OutPacket( + int packetId, + Array reasonCodes, + Array userProperties, + String reason) { + super(packetId); + this.reasonCodes = reasonCodes; + this.userProperties = userProperties; + this.reason = reason; + } + + @Override + protected boolean isPropertiesSupported() { + return true; + } + + @Override + public int getExpectedLength() { + return -1; + } + + @Override + protected void writeProperties(ByteBuffer buffer) { + + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901182 + writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); + writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); + } + + @Override + protected void writePayload(ByteBuffer buffer) { + // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901185 + for (var reasonCode : reasonCodes) { + writeByte(buffer, reasonCode.getValue()); + } + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/package-info.java new file mode 100644 index 00000000..4306f847 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/out/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.network.packet.out; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/package-info.java new file mode 100644 index 00000000..274828fa --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/network/packet/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.network.packet; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt311PacketOutFactory.java b/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt311PacketOutFactory.java new file mode 100644 index 00000000..984db040 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt311PacketOutFactory.java @@ -0,0 +1,150 @@ +package javasabr.mqtt.legacy.out; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.out.ConnectAck311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.Disconnect311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.legacy.network.packet.out.PingRequest311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PingResponse311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.Publish311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishAck311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishComplete311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishOutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishReceived311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishRelease311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.SubscribeAck311OutPacket; +import javasabr.mqtt.legacy.network.packet.out.UnsubscribeAck311OutPacket; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.MutableArray; + +public class Mqtt311PacketOutFactory extends MqttPacketOutFactory { + + @Override + public MqttWritablePacket newConnectAck( + MqttClient client, + ConnectAckReasonCode reasonCode, + boolean sessionPresent, + String requestedClientId, + long requestedSessionExpiryInterval, + int requestedKeepAlive, + int requestedReceiveMax, + String reason, + String serverReference, + String responseInformation, + String authenticationMethod, + byte[] authenticationData, + MutableArray userProperties) { + return new ConnectAck311OutPacket(reasonCode, sessionPresent); + } + + @Override + public PublishOutPacket newPublish( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + int topicAlias, + byte[] payload, + boolean stringPayload, + String responseTopic, + byte[] correlationData, + MutableArray userProperties) { + return new Publish311OutPacket(packetId, qos, retained, duplicate, topicName, payload); + } + + @Override + public MqttWritablePacket newPublishAck( + int packetId, + PublishAckReasonCode reasonCode, + String reason, + MutableArray userProperties) { + return new PublishAck311OutPacket(packetId); + } + + @Override + public MqttWritablePacket newSubscribeAck( + int packetId, + Array reasonCodes, + String reason, + MutableArray userProperties) { + return new SubscribeAck311OutPacket(reasonCodes, packetId); + } + + @Override + public MqttWritablePacket newUnsubscribeAck( + int packetId, + Array reasonCodes, + MutableArray userProperties, + String reason) { + return new UnsubscribeAck311OutPacket(packetId); + } + + @Override + public MqttWritablePacket newDisconnect( + MqttClient client, + DisconnectReasonCode reasonCode, + MutableArray userProperties, + String reason, + String serverReference) { + return new Disconnect311OutPacket(); + } + + @Override + public MqttWritablePacket newAuthenticate( + AuthenticateReasonCode reasonCode, + String authenticateMethod, + byte[] authenticateData, + MutableArray userProperties, + String reason) { + throw new UnsupportedOperationException(); + } + + @Override + public MqttWritablePacket newPingRequest() { + return new PingRequest311OutPacket(); + } + + @Override + public MqttWritablePacket newPingResponse() { + return new PingResponse311OutPacket(); + } + + @Override + public MqttWritablePacket newPublishRelease( + int packetId, + PublishReleaseReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishRelease311OutPacket(packetId); + } + + @Override + public MqttWritablePacket newPublishReceived( + int packetId, + PublishReceivedReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishReceived311OutPacket(packetId); + } + + @Override + public MqttWritablePacket newPublishCompleted( + int packetId, + PublishCompletedReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishComplete311OutPacket(packetId); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt5PacketOutFactory.java b/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt5PacketOutFactory.java new file mode 100644 index 00000000..cc17daff --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/out/Mqtt5PacketOutFactory.java @@ -0,0 +1,179 @@ +package javasabr.mqtt.legacy.out; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.out.Authentication5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.ConnectAck5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.Disconnect5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.legacy.network.packet.out.Publish5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishAck5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishComplete5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishOutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishReceived5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.PublishRelease5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.SubscribeAck5OutPacket; +import javasabr.mqtt.legacy.network.packet.out.UnsubscribeAck5OutPacket; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.MutableArray; + +public class Mqtt5PacketOutFactory extends Mqtt311PacketOutFactory { + + @Override + public MqttWritablePacket newConnectAck( + MqttClient client, + ConnectAckReasonCode reasonCode, + boolean sessionPresent, + String requestedClientId, + long requestedSessionExpiryInterval, + int requestedKeepAlive, + int requestedReceiveMax, + String reason, + String serverReference, + String responseInformation, + String authenticationMethod, + byte[] authenticationData, + MutableArray userProperties) { + var config = client.getConnectionConfig(); + return new ConnectAck5OutPacket( + reasonCode, + sessionPresent, + requestedClientId, + requestedSessionExpiryInterval, + requestedKeepAlive, + requestedReceiveMax, + reason, + serverReference, + responseInformation, + authenticationMethod, + authenticationData, + userProperties, + client.getClientId(), + config.getMaxQos(), + client.getSessionExpiryInterval(), + client.getMaximumPacketSize(), + client.getReceiveMax(), + client.getTopicAliasMaximum(), + client.getKeepAlive(), + config.isRetainAvailable(), + config.isWildcardSubscriptionAvailable(), + config.isSubscriptionIdAvailable(), + config.isSharedSubscriptionAvailable()); + } + + @Override + public PublishOutPacket newPublish( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + int topicAlias, + byte[] payload, + boolean stringPayload, + String responseTopic, + byte[] correlationData, + MutableArray userProperties) { + return new Publish5OutPacket( + packetId, + qos, + retained, + duplicate, + topicName, + payload, + topicAlias, + stringPayload, + responseTopic, + correlationData, + userProperties); + } + + @Override + public MqttWritablePacket newPublishAck( + int packetId, + PublishAckReasonCode reasonCode, + String reason, + MutableArray userProperties) { + return new PublishAck5OutPacket(packetId, reasonCode, userProperties, reason); + } + + @Override + public MqttWritablePacket newSubscribeAck( + int packetId, + Array reasonCodes, + String reason, + MutableArray userProperties) { + return new SubscribeAck5OutPacket(packetId, reasonCodes, userProperties, reason); + } + + @Override + public MqttWritablePacket newUnsubscribeAck( + int packetId, + Array reasonCodes, + MutableArray userProperties, + String reason) { + return new UnsubscribeAck5OutPacket(packetId, reasonCodes, userProperties, reason); + } + + @Override + public MqttWritablePacket newDisconnect( + MqttClient client, + DisconnectReasonCode reasonCode, + MutableArray userProperties, + String reason, + String serverReference) { + return new Disconnect5OutPacket( + reasonCode, + userProperties, + reason, + serverReference, + client.getSessionExpiryInterval()); + } + + @Override + public MqttWritablePacket newAuthenticate( + AuthenticateReasonCode reasonCode, + String authenticateMethod, + byte[] authenticateData, + MutableArray userProperties, + String reason) { + return new Authentication5OutPacket(userProperties, reasonCode, reason, authenticateMethod, authenticateData); + } + + @Override + public MqttWritablePacket newPublishRelease( + int packetId, + PublishReleaseReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishRelease5OutPacket(packetId, reasonCode, userProperties, reason); + } + + @Override + public MqttWritablePacket newPublishReceived( + int packetId, + PublishReceivedReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishReceived5OutPacket(packetId, reasonCode, userProperties, reason); + } + + @Override + public MqttWritablePacket newPublishCompleted( + int packetId, + PublishCompletedReasonCode reasonCode, + MutableArray userProperties, + String reason) { + return new PublishComplete5OutPacket(packetId, reasonCode, userProperties, reason); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactories.java b/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactories.java new file mode 100644 index 00000000..325ffbe7 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactories.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.legacy.out; + +import javasabr.mqtt.model.MqttVersion; +import javasabr.rlib.common.util.ArrayUtils; + +public class MqttPacketOutFactories { + + private static final MqttPacketOutFactory[] FACTORIES = ArrayUtils + .array( + new Mqtt311PacketOutFactory(), + new Mqtt311PacketOutFactory(), + new Mqtt5PacketOutFactory()); + + public static MqttPacketOutFactory of(MqttVersion version) { + return FACTORIES[version.ordinal()]; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactory.java b/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactory.java new file mode 100644 index 00000000..1f18bbb9 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/out/MqttPacketOutFactory.java @@ -0,0 +1,212 @@ +package javasabr.mqtt.legacy.out; + +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.data.type.StringPair; +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode; +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode; +import javasabr.mqtt.model.reason.code.DisconnectReasonCode; +import javasabr.mqtt.model.reason.code.PublishAckReasonCode; +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode; +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket; +import javasabr.mqtt.legacy.network.packet.out.PublishOutPacket; +import javasabr.rlib.collections.array.Array; +import javasabr.rlib.collections.array.MutableArray; +import javasabr.rlib.common.util.ArrayUtils; +import javasabr.rlib.common.util.StringUtils; + +public abstract class MqttPacketOutFactory { + + public abstract MqttWritablePacket newConnectAck( + MqttClient client, + ConnectAckReasonCode reasonCode, + boolean sessionPresent, + String requestedClientId, + long requestedSessionExpiryInterval, + int requestedKeepAlive, + int requestedReceiveMax, + String reason, + String serverReference, + String responseInformation, + String authenticationMethod, + byte[] authenticationData, + MutableArray userProperties); + + public MqttWritablePacket newConnectAck( + MqttClient client, + ConnectAckReasonCode reasonCode, + boolean sessionPresent, + String requestedClientId, + long requestedSessionExpiryInterval, + int requestedKeepAlive, + int requestedReceiveMax) { + return newConnectAck( + client, + reasonCode, + sessionPresent, + requestedClientId, + requestedSessionExpiryInterval, + requestedKeepAlive, + requestedReceiveMax, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + ArrayUtils.EMPTY_BYTE_ARRAY, + MutableArray.ofType(StringPair.class)); + } + + public MqttWritablePacket newConnectAck(MqttClient client, ConnectAckReasonCode reasonCode) { + return newConnectAck( + client, + reasonCode, + false, + StringUtils.EMPTY, + client.getSessionExpiryInterval(), + client.getKeepAlive(), + client.getReceiveMax(), + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + StringUtils.EMPTY, + ArrayUtils.EMPTY_BYTE_ARRAY, + MutableArray.ofType(StringPair.class)); + } + + public PublishOutPacket newPublish( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + byte[] payload) { + return newPublish( + packetId, + qos, + retained, + duplicate, + topicName, + 0, + payload, + false, + StringUtils.EMPTY, + ArrayUtils.EMPTY_BYTE_ARRAY, + MutableArray.ofType(StringPair.class)); + } + + public abstract PublishOutPacket newPublish( + int packetId, + QoS qos, + boolean retained, + boolean duplicate, + String topicName, + int topicAlias, + byte[] payload, + boolean stringPayload, + String responseTopic, + byte[] correlationData, + MutableArray userProperties); + + public abstract MqttWritablePacket newPublishAck( + int packetId, + PublishAckReasonCode reasonCode, + String reason, + MutableArray userProperties); + + public MqttWritablePacket newPublishAck(int packetId, PublishAckReasonCode reasonCode) { + return newPublishAck(packetId, reasonCode, StringUtils.EMPTY, MutableArray.ofType(StringPair.class)); + } + + public abstract MqttWritablePacket newSubscribeAck( + int packetId, + Array reasonCodes, + String reason, + MutableArray userProperties); + + public MqttWritablePacket newSubscribeAck(int packetId, Array reasonCodes) { + return newSubscribeAck(packetId, reasonCodes, StringUtils.EMPTY, MutableArray.ofType(StringPair.class)); + } + + public abstract MqttWritablePacket newUnsubscribeAck( + int packetId, + Array reasonCodes, + MutableArray userProperties, + String reason); + + public MqttWritablePacket newUnsubscribeAck(int packetId, Array reasonCodes) { + return newUnsubscribeAck(packetId, reasonCodes, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); + } + + public abstract MqttWritablePacket newDisconnect( + MqttClient client, + DisconnectReasonCode reasonCode, + MutableArray userProperties, + String reason, + String serverReference); + + public MqttWritablePacket newDisconnect(MqttClient client, DisconnectReasonCode reasonCode) { + return newDisconnect( + client, + reasonCode, + MutableArray.ofType(StringPair.class), + StringUtils.EMPTY, + StringUtils.EMPTY); + } + + public abstract MqttWritablePacket newAuthenticate( + AuthenticateReasonCode reasonCode, + String authenticateMethod, + byte[] authenticateData, + MutableArray userProperties, + String reason); + + public MqttWritablePacket newAuthenticate( + AuthenticateReasonCode reasonCode, + String authenticateMethod, + byte[] authenticateData) { + return newAuthenticate( + reasonCode, + authenticateMethod, + authenticateData, + MutableArray.ofType(StringPair.class), + StringUtils.EMPTY); + } + + public abstract MqttWritablePacket newPingRequest(); + + public abstract MqttWritablePacket newPingResponse(); + + public abstract MqttWritablePacket newPublishRelease( + int packetId, + PublishReleaseReasonCode reasonCode, + MutableArray userProperties, + String reason); + + public MqttWritablePacket newPublishRelease(int packetId, PublishReleaseReasonCode reasonCode) { + return newPublishRelease(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); + } + + public abstract MqttWritablePacket newPublishReceived( + int packetId, + PublishReceivedReasonCode reasonCode, + MutableArray userProperties, + String reason); + + public MqttWritablePacket newPublishReceived(int packetId, PublishReceivedReasonCode reasonCode) { + return newPublishReceived(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); + } + + public abstract MqttWritablePacket newPublishCompleted( + int packetId, + PublishCompletedReasonCode reasonCode, + MutableArray userProperties, + String reason); + + public MqttWritablePacket newPublishCompleted(int packetId, PublishCompletedReasonCode reasonCode) { + return newPublishCompleted(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); + } +} diff --git a/src/main/java/com/ss/mqtt/broker/model/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/out/package-info.java similarity index 62% rename from src/main/java/com/ss/mqtt/broker/model/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/out/package-info.java index bc753686..e7c8ebbf 100644 --- a/src/main/java/com/ss/mqtt/broker/model/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/out/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.model; +package javasabr.mqtt.legacy.out; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/AuthenticationService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/AuthenticationService.java new file mode 100644 index 00000000..0c56e81e --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/AuthenticationService.java @@ -0,0 +1,7 @@ +package javasabr.mqtt.legacy.service; + +import reactor.core.publisher.Mono; + +public interface AuthenticationService { + Mono auth(String userName, byte[] password); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/ClientIdRegistry.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/ClientIdRegistry.java new file mode 100644 index 00000000..7e695ac3 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/ClientIdRegistry.java @@ -0,0 +1,14 @@ +package javasabr.mqtt.legacy.service; + +import reactor.core.publisher.Mono; + +public interface ClientIdRegistry { + + Mono register(String clientId); + + Mono unregister(String clientId); + + boolean validate(String clientId); + + Mono generate(); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/CredentialSource.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/CredentialSource.java new file mode 100644 index 00000000..4b6853b9 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/CredentialSource.java @@ -0,0 +1,10 @@ +package javasabr.mqtt.legacy.service; + +import reactor.core.publisher.Mono; + +public interface CredentialSource { + + Mono check(String user, byte[] pass); + + Mono check(byte[] pass); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/MqttSessionService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/MqttSessionService.java new file mode 100644 index 00000000..f472752a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/MqttSessionService.java @@ -0,0 +1,13 @@ +package javasabr.mqtt.legacy.service; + +import javasabr.mqtt.legacy.network.MqttSession; +import reactor.core.publisher.Mono; + +public interface MqttSessionService { + + Mono restore(String clientId); + + Mono create(String clientId); + + Mono store(String clientId, MqttSession session, long expiryInterval); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/PublishingService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/PublishingService.java new file mode 100644 index 00000000..965390a5 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/PublishingService.java @@ -0,0 +1,9 @@ +package javasabr.mqtt.legacy.service; + +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; + +public interface PublishingService { + + void publish(MqttClient client, PublishInPacket publish); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/SubscriptionService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/SubscriptionService.java new file mode 100644 index 00000000..93d88027 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/SubscriptionService.java @@ -0,0 +1,58 @@ +package javasabr.mqtt.legacy.service; + +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.legacy.network.MqttClient; +import java.util.function.BiFunction; +import javasabr.rlib.collections.array.Array; + +/** + * Subscription service + */ +public interface SubscriptionService { + + /** + * Runs function for each topic subscriber + * + * @param topicName topic name + * @param argument additional argument + * @param action function to run + * @return {@link ActionResult} of function + */ + ActionResult forEachTopicSubscriber( + TopicName topicName, + A argument, + BiFunction action); + + /** + * Adds MQTT client to topic filter subscribers + * + * @param mqttClient MQTT client to be added + * @param topicFilters topic filters + * @return array of subscribe ack reason codes + */ + Array subscribe( + MqttClient mqttClient, + Array topicFilters); + + /** + * Removes MQTT client from subscribers by array of topic names + * + * @param mqttClient MQTT client to be removed + * @param topicFilters topic filters + * @return array of unsubscribe ack reason codes + */ + Array unsubscribe( + MqttClient mqttClient, + Array topicFilters); + + void cleanSubscriptions(MqttClient mqttClient, MqttSession mqttSession); + + void restoreSubscriptions(MqttClient mqttClient, MqttSession mqttSession); +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/AbstractCredentialSource.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/AbstractCredentialSource.java new file mode 100644 index 00000000..eca3bbcf --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/AbstractCredentialSource.java @@ -0,0 +1,44 @@ +package javasabr.mqtt.legacy.service.impl; + +import javasabr.mqtt.legacy.service.CredentialSource; +import java.util.Arrays; +import javasabr.rlib.collections.dictionary.DictionaryFactory; +import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; +import reactor.core.publisher.Mono; + +public abstract class AbstractCredentialSource implements CredentialSource { + + private final LockableRefToRefDictionary credentials = + DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); + + abstract void init(); + + void putAll(RefToRefDictionary otherCredentials) { + long stamp = credentials.writeLock(); + try { + credentials.append(otherCredentials); + } finally { + credentials.writeUnlock(stamp); + } + } + + void put(String user, byte[] pass) { + long stamp = credentials.writeLock(); + try { + credentials.put(user, pass); + } finally { + credentials.writeUnlock(stamp); + } + } + + @Override + public Mono check(String user, byte[] pass) { + return Mono.just(Arrays.equals(pass, credentials.get(user))); + } + + @Override + public Mono check(byte[] pass) { + return Mono.just(Boolean.FALSE); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/DefaultPublishingService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/DefaultPublishingService.java new file mode 100644 index 00000000..8fbf1451 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/DefaultPublishingService.java @@ -0,0 +1,19 @@ +package javasabr.mqtt.legacy.service.impl; + +import javasabr.mqtt.legacy.handler.publish.in.PublishInHandler; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket; +import javasabr.mqtt.legacy.service.PublishingService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DefaultPublishingService implements PublishingService { + + private final PublishInHandler[] publishInHandlers; + + @Override + public void publish(MqttClient client, PublishInPacket publish) { + PublishInHandler publishInHandler = publishInHandlers[publish.getQos().ordinal()]; + publishInHandler.handle(client, publish); + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/FileCredentialsSource.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/FileCredentialsSource.java new file mode 100644 index 00000000..e303cc6d --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/FileCredentialsSource.java @@ -0,0 +1,47 @@ +package javasabr.mqtt.legacy.service.impl; + +import javasabr.mqtt.legacy.exception.CredentialsSourceException; +import java.io.FileInputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Properties; +import javasabr.rlib.collections.dictionary.DictionaryCollectors; +import javasabr.rlib.collections.dictionary.RefToRefDictionary; + +public class FileCredentialsSource extends AbstractCredentialSource { + + private final String fileName; + + public FileCredentialsSource(String fileName) { + this.fileName = fileName; + init(); + } + + @Override + void init() { + URL credentialUrl = FileCredentialsSource.class + .getClassLoader() + .getResource(fileName); + + if (credentialUrl == null) { + throw new CredentialsSourceException("Credentials file could not be found"); + } + + try { + var credentialsProperties = new Properties(); + credentialsProperties.load(new FileInputStream(credentialUrl.getPath())); + + RefToRefDictionary credentials = credentialsProperties + .entrySet() + .stream() + .collect(DictionaryCollectors.toRefToRefDictionary( + entry -> entry.getKey().toString(), + entry -> entry.getValue().toString().getBytes(StandardCharsets.UTF_8))); + + putAll(credentials); + } catch (IOException e) { + throw new CredentialsSourceException(e); + } + } +} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/InMemoryClientIdRegistry.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryClientIdRegistry.java similarity index 93% rename from src/main/java/com/ss/mqtt/broker/service/impl/InMemoryClientIdRegistry.java rename to legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryClientIdRegistry.java index 9b00bd8d..539ecbdc 100644 --- a/src/main/java/com/ss/mqtt/broker/service/impl/InMemoryClientIdRegistry.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryClientIdRegistry.java @@ -1,6 +1,8 @@ -package com.ss.mqtt.broker.service.impl; +package javasabr.mqtt.legacy.service.impl; -import com.ss.mqtt.broker.service.ClientIdRegistry; +import javasabr.mqtt.legacy.service.ClientIdRegistry; +import java.util.BitSet; +import java.util.UUID; import javasabr.rlib.collections.dictionary.Dictionary; import javasabr.rlib.collections.dictionary.DictionaryFactory; import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; @@ -9,9 +11,6 @@ import lombok.experimental.FieldDefaults; import reactor.core.publisher.Mono; -import java.util.BitSet; -import java.util.UUID; - @FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) public class InMemoryClientIdRegistry implements ClientIdRegistry { @@ -79,9 +78,7 @@ public boolean validate(String clientId) { @Override public Mono generate() { while (true) { - String clientId = UUID - .randomUUID() - .toString(); + String clientId = UUID.randomUUID().toString(); boolean contains = clientIdRegistry .operations() .getBooleanInReadLock(clientId, Dictionary::containsKey); diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/InMemoryMqttSessionService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryMqttSessionService.java similarity index 90% rename from src/main/java/com/ss/mqtt/broker/service/impl/InMemoryMqttSessionService.java rename to legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryMqttSessionService.java index 1d956740..ca38ca31 100644 --- a/src/main/java/com/ss/mqtt/broker/service/impl/InMemoryMqttSessionService.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/InMemoryMqttSessionService.java @@ -1,9 +1,10 @@ -package com.ss.mqtt.broker.service.impl; +package javasabr.mqtt.legacy.service.impl; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.MqttSession.UnsafeMqttSession; -import com.ss.mqtt.broker.model.impl.DefaultMqttSession; -import com.ss.mqtt.broker.service.MqttSessionService; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.legacy.network.MqttSession.UnsafeMqttSession; +import javasabr.mqtt.legacy.network.DefaultMqttSession; +import javasabr.mqtt.legacy.service.MqttSessionService; +import java.io.Closeable; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.MutableArray; import javasabr.rlib.collections.dictionary.Dictionary; @@ -16,8 +17,6 @@ import lombok.extern.log4j.Log4j2; import reactor.core.publisher.Mono; -import java.io.Closeable; - @Log4j2 @FieldDefaults(level = AccessLevel.PRIVATE) public class InMemoryMqttSessionService implements MqttSessionService, Closeable { @@ -116,13 +115,11 @@ private static void removeExpiredSessions( MutableArray expired) { long time = System.currentTimeMillis(); for (UnsafeMqttSession session : expired) { - if (session.getExpirationTime() <= time) { continue; } UnsafeMqttSession removed = sessions.remove(session.getClientId()); - log.debug("Removed expired session for client:[{}]", session.getClientId()); // if we already have new session under the same client id @@ -134,13 +131,11 @@ private static void removeExpiredSessions( } } - private boolean findToRemove( - MutableArray toCheck, - MutableArray toRemove) { + private boolean findToRemove(MutableArray toCheck, MutableArray toRemove) { var currentTime = System.currentTimeMillis(); - for (var session : toCheck) { + for (UnsafeMqttSession session : toCheck) { if (session.getExpirationTime() > currentTime) { toRemove.add(session); } diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleAuthenticationService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleAuthenticationService.java new file mode 100644 index 00000000..c06e5c4c --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleAuthenticationService.java @@ -0,0 +1,22 @@ +package javasabr.mqtt.legacy.service.impl; + +import javasabr.mqtt.legacy.service.AuthenticationService; +import javasabr.mqtt.legacy.service.CredentialSource; +import lombok.RequiredArgsConstructor; +import reactor.core.publisher.Mono; + +@RequiredArgsConstructor +public class SimpleAuthenticationService implements AuthenticationService { + + private final CredentialSource credentialSource; + private final boolean allowAnonymousAuth; + + @Override + public Mono auth(String userName, byte[] password) { + if (allowAnonymousAuth && userName.isEmpty()) { + return Mono.just(Boolean.TRUE); + } else { + return credentialSource.check(userName, password); + } + } +} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/SimpleSubscriptionService.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleSubscriptionService.java similarity index 59% rename from src/main/java/com/ss/mqtt/broker/service/impl/SimpleSubscriptionService.java rename to legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleSubscriptionService.java index 1325e33a..a284a88e 100644 --- a/src/main/java/com/ss/mqtt/broker/service/impl/SimpleSubscriptionService.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/SimpleSubscriptionService.java @@ -1,22 +1,28 @@ -package com.ss.mqtt.broker.service.impl; +package javasabr.mqtt.legacy.service.impl; -import static com.ss.mqtt.broker.model.ActionResult.EMPTY; -import static com.ss.mqtt.broker.model.ActionResult.FAILED; -import static com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; -import static com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode.UNSPECIFIED_ERROR; -import static com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; -import static com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode.*; -import static com.ss.mqtt.broker.util.TopicUtils.*; +import static javasabr.mqtt.model.ActionResult.EMPTY; +import static javasabr.mqtt.model.ActionResult.FAILED; +import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED; +import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.UNSPECIFIED_ERROR; +import static javasabr.mqtt.model.reason.code.SubscribeAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED; +import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.NO_SUBSCRIPTION_EXISTED; +import static javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode.SUCCESS; +import static javasabr.mqtt.model.utils.TopicUtils.hasWildcard; +import static javasabr.mqtt.model.utils.TopicUtils.isInvalid; +import static javasabr.mqtt.model.utils.TopicUtils.isShared; -import com.ss.mqtt.broker.config.MqttConnectionConfig; -import com.ss.mqtt.broker.model.*; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.model.topic.TopicName; -import com.ss.mqtt.broker.model.topic.TopicSubscribers; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.service.SubscriptionService; +import javasabr.mqtt.model.MqttConnectionConfig; +import javasabr.mqtt.model.ActionResult; +import javasabr.mqtt.legacy.network.MqttSession; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.mqtt.model.topic.TopicSubscribers; +import javasabr.mqtt.legacy.network.MqttClient; +import javasabr.mqtt.legacy.service.SubscriptionService; import java.util.function.BiFunction; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayCollectors; @@ -34,7 +40,9 @@ public class SimpleSubscriptionService implements SubscriptionService { @Override public ActionResult forEachTopicSubscriber( - TopicName topicName, A arg1, BiFunction action) { + TopicName topicName, + A arg1, + BiFunction action) { if (isInvalid(topicName)) { return FAILED; } @@ -46,9 +54,7 @@ public ActionResult forEachTopicSubscriber( } @Override - public Array subscribe( - MqttClient mqttClient, - Array topicFilters) { + public Array subscribe(MqttClient mqttClient, Array topicFilters) { return topicFilters .stream() .map(topicFilter -> addSubscription(topicFilter, mqttClient)) @@ -56,9 +62,7 @@ public Array subscribe( } @Nullable - private SubscribeAckReasonCode addSubscription( - SubscribeTopicFilter subscribe, - MqttClient client) { + private SubscribeAckReasonCode addSubscription(SubscribeTopicFilter subscribe, MqttClient client) { MqttSession session = client.getSession(); if (session == null) { return null; @@ -83,9 +87,7 @@ private SubscribeAckReasonCode addSubscription( } @Override - public Array unsubscribe( - MqttClient mqttClient, - Array topicFilters) { + public Array unsubscribe(MqttClient mqttClient, Array topicFilters) { return topicFilters .stream() .map(topicFilter -> removeSubscription(topicFilter, mqttClient)) diff --git a/src/main/java/com/ss/mqtt/broker/handler/client/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/package-info.java similarity index 56% rename from src/main/java/com/ss/mqtt/broker/handler/client/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/service/impl/package-info.java index 15ac8538..f2c1a3eb 100644 --- a/src/main/java/com/ss/mqtt/broker/handler/client/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/impl/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.handler.client; +package javasabr.mqtt.legacy.service.impl; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/service/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/service/package-info.java new file mode 100644 index 00000000..98fb2425 --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/service/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.legacy.service; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/util/MqttDataUtils.java b/legacy/src/main/java/javasabr/mqtt/legacy/util/MqttDataUtils.java new file mode 100644 index 00000000..cfe42c1a --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/util/MqttDataUtils.java @@ -0,0 +1,84 @@ +package javasabr.mqtt.legacy.util; + +import java.nio.ByteBuffer; + +public class MqttDataUtils { + + public static int MAX_MBI = 268_435_455; + + /** + * Write a MQTT multi-byte integer to byte buffer. + * https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011 + * + * @throws IllegalArgumentException if number is too big. + */ + public static ByteBuffer writeMbi(int number, ByteBuffer buffer) { + + var sizeInBytes = 0; + var valueToWrite = number; + do { + + var digit = (byte) (valueToWrite % 128); + valueToWrite = valueToWrite / 128; + + if (valueToWrite > 0) { + digit |= 0x80; + } + + buffer.put(digit); + sizeInBytes++; + + } while (valueToWrite > 0); + + if (sizeInBytes > 4) { + throw new IllegalArgumentException(number + " is too big."); + } + + return buffer; + } + + /** + * Read a MQTT multi-byte integer from byte buffer. + * https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011 + * + * @return -1 if buffer's data isn't enough to read integer. + */ + public static int readMbi(ByteBuffer buffer) { + + var originalPos = buffer.position(); + + int result = 0; + int multiplier = 1; + + byte readValue; + do { + + if (!buffer.hasRemaining()) { + buffer.position(originalPos); + return -1; + } + + readValue = buffer.get(); + result += ((readValue & 0x7F) * multiplier); + multiplier *= 128; + + } while ((readValue & 0x80) != 0); + + return result; + } + + /** + * Get byte count of MQTT multi-byte integer. + */ + public static int sizeOfMbi(int number) { + + var sizeInBytes = 0; + var valueToWrite = number; + do { + valueToWrite = valueToWrite / 128; + sizeInBytes++; + } while (valueToWrite > 0); + + return sizeInBytes; + } +} diff --git a/legacy/src/main/java/javasabr/mqtt/legacy/util/ReactorUtils.java b/legacy/src/main/java/javasabr/mqtt/legacy/util/ReactorUtils.java new file mode 100644 index 00000000..cacc1fad --- /dev/null +++ b/legacy/src/main/java/javasabr/mqtt/legacy/util/ReactorUtils.java @@ -0,0 +1,70 @@ +package javasabr.mqtt.legacy.util; + +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import javasabr.rlib.common.util.StringUtils; +import reactor.core.publisher.Mono; + +public class ReactorUtils { + + public static Function ifTrue(Runnable function) { + return value -> { + if (value) { + function.run(); + } + return value; + }; + } + + public static Function ifTrue(A arg, Consumer function) { + return value -> { + if (value) { + function.accept(arg); + } + return value; + }; + } + + public static Function> ifNotEmpty( + Function> toContinue, + Supplier> another) { + return value -> { + if (StringUtils.isNotEmpty(value)) { + return toContinue.apply(value); + } else { + return another.get(); + } + }; + } + + public static Function> ifTrue(Supplier> function, Runnable another) { + return value -> { + + if (!value) { + another.run(); + return Mono.empty(); + } + + return function.get(); + }; + } + + public static Function> ifTrue( + T1 arg1, + T2 arg2, + BiFunction> function, + T3 arg3, + Consumer another) { + return value -> { + + if (value) { + return function.apply(arg1, arg2); + } + + another.accept(arg3); + return Mono.empty(); + }; + } +} diff --git a/src/main/java/com/ss/mqtt/broker/config/package-info.java b/legacy/src/main/java/javasabr/mqtt/legacy/util/package-info.java similarity index 61% rename from src/main/java/com/ss/mqtt/broker/config/package-info.java rename to legacy/src/main/java/javasabr/mqtt/legacy/util/package-info.java index 9dd80db1..9eb5821b 100644 --- a/src/main/java/com/ss/mqtt/broker/config/package-info.java +++ b/legacy/src/main/java/javasabr/mqtt/legacy/util/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.config; +package javasabr.mqtt.legacy.util; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/resources/application.properties b/legacy/src/main/resources/application.properties similarity index 100% rename from src/main/resources/application.properties rename to legacy/src/main/resources/application.properties diff --git a/src/main/resources/credentials b/legacy/src/main/resources/credentials similarity index 100% rename from src/main/resources/credentials rename to legacy/src/main/resources/credentials diff --git a/src/main/resources/log4j2.xml b/legacy/src/main/resources/log4j2.xml similarity index 100% rename from src/main/resources/log4j2.xml rename to legacy/src/main/resources/log4j2.xml diff --git a/src/test/groovy/com/ss/mqtt/broker/test/UnitSpecification.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/UnitSpecification.groovy similarity index 72% rename from src/test/groovy/com/ss/mqtt/broker/test/UnitSpecification.groovy rename to legacy/src/test/groovy/javasabr/mqtt/legacy/UnitSpecification.groovy index 1faebe21..f9174689 100644 --- a/src/test/groovy/com/ss/mqtt/broker/test/UnitSpecification.groovy +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/UnitSpecification.groovy @@ -1,4 +1,4 @@ -package com.ss.mqtt.broker.test +package javasabr.mqtt.legacy import spock.lang.Specification diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/extension/SpecificationExtensions.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/extension/SpecificationExtensions.groovy new file mode 100644 index 00000000..dc2e6a89 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/extension/SpecificationExtensions.groovy @@ -0,0 +1,78 @@ +package javasabr.mqtt.legacy.extension + +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket +import javasabr.mqtt.legacy.util.MqttDataUtils +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.data.type.PacketDataType +import javasabr.mqtt.model.data.type.StringPair +import javasabr.rlib.collections.array.Array +import javasabr.rlib.collections.array.IntArray +import spock.lang.Specification + +import java.nio.ByteBuffer + +class SpecificationExtensions extends Specification { + + static final writer = new MqttWritablePacket() { + + @Override + protected void writeImpl(ByteBuffer buffer) {} + } + + static ByteBuffer putMbi(ByteBuffer self, int value) { + MqttDataUtils.writeMbi(value, self) + return self + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, boolean value) { + return putProperty(self, property, value ? 1 : 0) + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, long value) { + writer.writeProperty(self, property, value) + return self + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, byte[] value) { + writer.writeProperty(self, property, value) + return self + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, String value) { + writer.writeProperty(self, property, value) + return self + } + + static ByteBuffer putString(ByteBuffer self, String value) { + writer.writeString(self, value) + return self + } + + static ByteBuffer putBytes(ByteBuffer self, byte[] value) { + writer.writeBytes(self, value) + return self + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, Array values) { + + switch (property.getDataType()) { + case PacketDataType.UTF_8_STRING_PAIR: + writer.writeStringPairProperties(self, property, values as Array) + break + default: + throw new IllegalStateException() + } + + return self + } + + static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, IntArray values) { + values.each { writer.writeProperty(self, property, it) } + return self + } + + static ByteBuffer putBoolean(ByteBuffer self, boolean value) { + self.put((value ? 1 : 0) as byte) + return self + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ConnectSubscribePublishTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ConnectSubscribePublishTest.groovy new file mode 100644 index 00000000..d7895121 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ConnectSubscribePublishTest.groovy @@ -0,0 +1,264 @@ +package javasabr.mqtt.legacy.integration + +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient +import com.hivemq.client.mqtt.mqtt3.message.Mqtt3MessageType +import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish +import com.hivemq.client.mqtt.mqtt3.message.subscribe.suback.Mqtt3SubAckReturnCode +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import com.hivemq.client.mqtt.mqtt5.message.Mqtt5MessageType +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator +import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish +import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode + +import java.util.concurrent.atomic.AtomicReference + +class ConnectSubscribePublishTest extends IntegrationSpecification { + + def "publisher should publish message QoS 0 using mqtt 3.1.1"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt311Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt311Client() + when: + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_0) + subscribeResult.type == Mqtt3MessageType.SUBACK + + publishResult != null + publishResult.qos == MqttQos.AT_MOST_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.AT_MOST_ONCE + received.get().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "publisher should publish message QoS 0 using mqtt 5"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt5Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt5Client() + when: + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_0) + subscribeResult.type == Mqtt5MessageType.SUBACK + + publishResult != null + publishResult.publish.qos == MqttQos.AT_MOST_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.AT_MOST_ONCE + received.get().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "publisher should publish message QoS 1 using mqtt 3.1.1"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt311Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt311Client() + when: + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_1) + subscribeResult.type == Mqtt3MessageType.SUBACK + + publishResult != null + publishResult.qos == MqttQos.AT_LEAST_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.AT_LEAST_ONCE + received.get().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "publisher should publish message QoS 1 using mqtt 5"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt5Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt5Client() + when: + + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_1) + subscribeResult.type == Mqtt5MessageType.SUBACK + + publishResult != null + publishResult.publish.qos == MqttQos.AT_LEAST_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.AT_LEAST_ONCE + received.get().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "publisher should publish message QoS 2 using mqtt 3.1.1"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt311Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt311Client() + when: + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.EXACTLY_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.EXACTLY_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_2) + subscribeResult.type == Mqtt3MessageType.SUBACK + + publishResult != null + publishResult.qos == MqttQos.EXACTLY_ONCE + publishResult.type == Mqtt3MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.EXACTLY_ONCE + received.get().type == Mqtt3MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def "publisher should publish message QoS 2 using mqtt 5"() { + given: + def received = new AtomicReference() + def subscriber = buildExternalMqtt5Client() + def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() + def publisher = buildExternalMqtt5Client() + when: + + subscriber.connect().join() + publisher.connect().join() + + def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.EXACTLY_ONCE, received) + def publishResult = publish(publisher, subscriberId, MqttQos.EXACTLY_ONCE) + + Thread.sleep(100) + then: + noExceptionThrown() + + subscribeResult != null + subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_2) + subscribeResult.type == Mqtt5MessageType.SUBACK + + publishResult != null + publishResult.publish.qos == MqttQos.EXACTLY_ONCE + publishResult.publish.type == Mqtt5MessageType.PUBLISH + + received.get() != null + received.get().qos == MqttQos.EXACTLY_ONCE + received.get().type == Mqtt5MessageType.PUBLISH + cleanup: + subscriber.disconnect().join() + publisher.disconnect().join() + } + + def publish(Mqtt5AsyncClient publisher, String subscriberId, MqttQos qos) { + return publisher.publishWith() + .topic("test/$subscriberId") + .qos(qos) + .payload(publishPayload) + .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) + .send() + .join() + } + + def subscribe( + Mqtt5AsyncClient subscriber, + String subscriberId, + MqttQos qos, + AtomicReference received + ) { + return subscriber.subscribeWith() + .topicFilter("test/$subscriberId") + .qos(qos) + .callback({ publish -> received.set(publish) }) + .send() + .join() + } + + def publish(Mqtt3AsyncClient publisher, String subscriberId, MqttQos qos) { + return publisher.publishWith() + .topic("test/$subscriberId") + .qos(qos) + .payload(publishPayload) + .send() + .join() + } + + def subscribe( + Mqtt3AsyncClient subscriber, + String subscriberId, + MqttQos qos, + AtomicReference received + ) { + return subscriber.subscribeWith() + .topicFilter("test/$subscriberId") + .qos(qos) + .callback({ publish -> received.set(publish) }) + .send() + .join() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ExternalConnectionTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ExternalConnectionTest.groovy new file mode 100644 index 00000000..9caa6909 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/ExternalConnectionTest.groovy @@ -0,0 +1,179 @@ +package javasabr.mqtt.legacy.integration + +import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException +import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.legacy.network.packet.out.Connect311OutPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.rlib.common.util.ArrayUtils +import spock.lang.Ignore + +import java.nio.charset.StandardCharsets +import java.util.concurrent.CompletionException + +class ExternalConnectionTest extends IntegrationSpecification { + + def "client should connect to broker without user and pass using mqtt 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + def result = client.connect().join() + then: + result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker without user and pass using mqtt 5"() { + given: + def client = buildExternalMqtt5Client() + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + result.sessionExpiryInterval.present + result.sessionExpiryInterval.getAsLong() == MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT + result.serverKeepAlive.present + result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED + !result.serverReference.present + !result.responseInformation.present + !result.assignedClientIdentifier.present + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker with user and pass using mqtt 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + def result = connectWith(client, 'user1', 'password') + then: + result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker with user and pass using mqtt 5"() { + given: + def client = buildExternalMqtt5Client() + when: + def result = connectWith(client, 'user1', 'password') + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + result.sessionExpiryInterval.present + result.sessionExpiryInterval.getAsLong() == MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT + result.serverKeepAlive.present + result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED + !result.serverReference.present + !result.responseInformation.present + !result.assignedClientIdentifier.present + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should not connect to broker without providing a client id using mqtt 3.1.1"() { + given: + def client = buildExternalMqtt311Client("") + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED + } + + def "client should connect to broker without providing a client id using mqtt 5"() { + given: + def client = buildExternalMqtt5Client("") + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + result.assignedClientIdentifier.present + result.assignedClientIdentifier.get().toString() != "" + cleanup: + client.disconnect().join() + } + + def "client should not connect to broker with invalid client id using mqtt 3.1.1"(String clientId) { + given: + def client = buildExternalMqtt311Client(clientId) + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED + where: + clientId << ["!@#!@*()^&"] + } + + def "client should not connect to broker with invalid client id using mqtt 5"(String clientId) { + given: + def client = buildExternalMqtt5Client(clientId) + when: + client.connect().join() + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt5ConnAckException + cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID + where: + clientId << ["!@#!@*()^&"] + } + + def "client should not connect to broker with wrong pass using mqtt 3.1.1"() { + given: + def client = buildExternalMqtt311Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt3ConnAckException + cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD + } + + @Ignore + def "client should not connect to broker without username and with pass using mqtt 3.1.1"() { + given: + def client = buildMqtt311MockClient() + def clientId = generateClientId() + when: + + client.connect() + client.send(new Connect311OutPacket( + "", + "", + clientId, + "wrongPassword".getBytes(StandardCharsets.UTF_8), + ArrayUtils.EMPTY_BYTE_ARRAY, + QoS.AT_MOST_ONCE, + keepAlive, + false, + false + )) + + def connectAck = client.readNext() as ConnectAckInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } + + def "client should not connect to broker with wrong pass using mqtt 5"() { + given: + def client = buildExternalMqtt5Client() + when: + connectWith(client, "user", "wrongPassword") + then: + def ex = thrown CompletionException + def cause = ex.cause as Mqtt5ConnAckException + cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/IntegrationSpecification.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/IntegrationSpecification.groovy new file mode 100644 index 00000000..af798a55 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/IntegrationSpecification.groovy @@ -0,0 +1,175 @@ +package javasabr.mqtt.legacy.integration + +import com.hivemq.client.mqtt.MqttClient +import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient +import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient +import javasabr.mqtt.legacy.network.MqttConnection +import javasabr.mqtt.legacy.integration.config.MqttBrokerTestConfig +import javasabr.mqtt.legacy.mock.MqttMockClient +import javasabr.mqtt.model.MqttConnectionConfig +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.MqttVersion +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig +import spock.lang.Specification + +import java.nio.charset.StandardCharsets +import java.util.concurrent.atomic.AtomicInteger + +import static javasabr.mqtt.legacy.network.MqttClient.* + +@SpringJUnitConfig(classes = MqttBrokerTestConfig) +class IntegrationSpecification extends Specification { + + public static final encoding = StandardCharsets.UTF_8 + public static final topicFilter = "topic/Filter" + public static final publishPayload = "publishPayload".getBytes(encoding) + public static final clientId = "testClientId" + public static final keepAlive = 120 + + private static final idGenerator = new AtomicInteger(1) + + @Autowired + InetSocketAddress externalNetworkAddress + + @Autowired + InetSocketAddress internalNetworkAddress + + @Autowired + MqttConnectionConfig externalConnectionConfig + + def buildExternalMqtt311Client() { + return buildMqtt311Client(generateClientId(), externalNetworkAddress) + } + + def buildInternalMqtt311Client() { + return buildMqtt311Client(generateClientId(), internalNetworkAddress) + } + + def buildExternalMqtt5Client() { + return buildMqtt5Client(generateClientId(), externalNetworkAddress) + } + + def buildInternalMqtt5Client() { + return buildMqtt5Client(generateClientId(), internalNetworkAddress) + } + + def buildExternalMqtt311Client(String clientId) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(externalNetworkAddress.getHostName()) + .serverPort(externalNetworkAddress.getPort()) + .useMqttVersion3() + .build() + .toAsync() + } + + def buildMqtt311Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion3() + .build() + .toAsync() + } + + def buildExternalMqtt5Client(String clientId) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(externalNetworkAddress.getHostName()) + .serverPort(externalNetworkAddress.getPort()) + .useMqttVersion5() + .build() + .toAsync() + } + + def buildMqtt5Client(String clientId, InetSocketAddress address) { + return MqttClient.builder() + .identifier(clientId) + .serverHost(address.getHostName()) + .serverPort(address.getPort()) + .useMqttVersion5() + .build() + .toAsync() + } + + def generateClientId() { + return generateClientId("Default") + } + + def generateClientId(String prefix) { + return prefix + "_" + idGenerator.incrementAndGet() + } + + def connectWith(Mqtt3AsyncClient client, String user, String pass) { + return client.connectWith() + .simpleAuth() + .username(user) + .password(pass.getBytes(encoding)) + .applySimpleAuth() + .send() + .join() + } + + def connectWith(Mqtt5AsyncClient client, String user, String pass) { + return client.connectWith() + .simpleAuth() + .username(user) + .password(pass.getBytes(encoding)) + .applySimpleAuth() + .send() + .join() + } + + def buildMqtt5MockClient() { + return new MqttMockClient( + externalNetworkAddress.getHostName(), + externalNetworkAddress.getPort(), + mqtt5MockedConnection(externalConnectionConfig) + ) + } + + def buildMqtt311MockClient() { + return new MqttMockClient( + externalNetworkAddress.getHostName(), + externalNetworkAddress.getPort(), + mqtt311MockedConnection(externalConnectionConfig) + ) + } + + def mqtt5MockedConnection(MqttConnectionConfig deviceConnectionConfig) { + + return Stub(MqttConnection) { + isSupported(MqttVersion.MQTT_5) >> true + isSupported(MqttVersion.MQTT_3_1_1) >> true + getConfig() >> deviceConnectionConfig + getClient() >> Stub(UnsafeMqttClient) { + getConnectionConfig() >> deviceConnectionConfig + getSessionExpiryInterval() >> MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED + getReceiveMax() >> deviceConnectionConfig.getReceiveMaximum() + getMaximumPacketSize() >> deviceConnectionConfig.getMaximumPacketSize() + getClientId() >> IntegrationSpecification.clientId + getKeepAlive() >> MqttProperties.SERVER_KEEP_ALIVE_DEFAULT + getTopicAliasMaximum() >> deviceConnectionConfig.getTopicAliasMaximum() + } + } + } + + def mqtt311MockedConnection(MqttConnectionConfig deviceConnectionConfig) { + return Stub(MqttConnection) { + isSupported(MqttVersion.MQTT_5) >> false + isSupported(MqttVersion.MQTT_3_1_1) >> true + getConfig() >> deviceConnectionConfig + getClient() >> Stub(UnsafeMqttClient) { + getConnectionConfig() >> deviceConnectionConfig + getSessionExpiryInterval() >> MqttProperties.SESSION_EXPIRY_INTERVAL_DISABLED + getReceiveMax() >> deviceConnectionConfig.getReceiveMaximum() + getMaximumPacketSize() >> deviceConnectionConfig.getMaximumPacketSize() + getClientId() >> IntegrationSpecification.clientId + getKeepAlive() >> MqttProperties.SERVER_KEEP_ALIVE_DEFAULT + getTopicAliasMaximum() >> deviceConnectionConfig.getTopicAliasMaximum() + } + } + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/InternalConnectionTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/InternalConnectionTest.groovy new file mode 100644 index 00000000..e9e62c08 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/InternalConnectionTest.groovy @@ -0,0 +1,39 @@ +package javasabr.mqtt.legacy.integration + +import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import javasabr.mqtt.model.MqttProperties + +class InternalConnectionTest extends IntegrationSpecification { + + def "client should connect to broker without user and pass using mqtt 3.1.1"() { + given: + def client = buildInternalMqtt311Client() + when: + def result = client.connect().join() + then: + result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS + !result.sessionPresent + cleanup: + client.disconnect().join() + } + + def "client should connect to broker without user and pass using mqtt 5"() { + given: + def client = buildInternalMqtt5Client() + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + result.sessionExpiryInterval.present + result.sessionExpiryInterval.getAsLong() == MqttProperties.SESSION_EXPIRY_INTERVAL_DEFAULT + result.serverKeepAlive.present + result.serverKeepAlive.getAsInt() == MqttProperties.SERVER_KEEP_ALIVE_DISABLED + !result.serverReference.present + !result.responseInformation.present + !result.assignedClientIdentifier.present + !result.sessionPresent + cleanup: + client.disconnect().join() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/PublishRetryTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/PublishRetryTest.groovy new file mode 100644 index 00000000..b742a977 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/PublishRetryTest.groovy @@ -0,0 +1,323 @@ +package javasabr.mqtt.legacy.integration + +import com.hivemq.client.mqtt.datatypes.MqttQos +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.Connect311OutPacket +import javasabr.mqtt.legacy.network.packet.out.Connect5OutPacket +import javasabr.mqtt.legacy.network.packet.out.PublishComplete311OutPacket +import javasabr.mqtt.legacy.network.packet.out.PublishComplete5OutPacket +import javasabr.mqtt.legacy.network.packet.out.PublishReceived311OutPacket +import javasabr.mqtt.legacy.network.packet.out.PublishReceived5OutPacket +import javasabr.mqtt.legacy.network.packet.out.Subscribe311OutPacket +import javasabr.mqtt.legacy.network.packet.out.Subscribe5OutPacket +import javasabr.mqtt.legacy.service.MqttSessionService +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.rlib.collections.array.Array +import org.springframework.beans.factory.annotation.Autowired + +class PublishRetryTest extends IntegrationSpecification { + + @Autowired + MqttSessionService mqttSessionService + + def "mqtt 3.1.1 client should be generate session with one pending QoS 1 packet"() { + given: + def publisher = buildExternalMqtt5Client() + def subscriber = buildMqtt311MockClient() + def subscriberId = generateClientId() + when: + + publisher.connect().join() + + subscriber.connect() + subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) + + def connectAck = subscriber.readNext() as ConnectAckInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + when: + + subscriber.send(new Subscribe311OutPacket( + Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), + 1 + )) + + def subscribeAck = subscriber.readNext() as SubscribeAckInPacket + + then: + subscribeAck.reasonCodes.stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) + when: + + publisher.publishWith() + .topic("test/retry/$subscriberId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + + def receivedPublish = subscriber.readNext() as PublishInPacket + + then: + receivedPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + def receivedDupPublish = subscriber.readNext() as PublishInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 5 client should be generate session with one pending QoS 1 packet"() { + given: + def publisher = buildExternalMqtt5Client() + def subscriber = buildMqtt5MockClient() + def subscriberId = generateClientId() + when: + + publisher.connect().join() + + subscriber.connect() + subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) + + def connectAck = subscriber.readNext() as ConnectAckInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + when: + + subscriber.send(new Subscribe5OutPacket( + Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), + 1 + )) + + def subscribeAck = subscriber.readNext() as SubscribeAckInPacket + + then: + subscribeAck.reasonCodes.stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) + when: + + publisher.publishWith() + .topic("test/retry/$subscriberId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + + def receivedPublish = subscriber.readNext() as PublishInPacket + + then: + receivedPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + def receivedDupPublish = subscriber.readNext() as PublishInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 3.1.1 client should be generate session with one pending QoS 2 packet"() { + given: + def publisher = buildExternalMqtt5Client() + def subscriber = buildMqtt311MockClient() + def subscriberId = generateClientId() + when: + + publisher.connect().join() + + subscriber.connect() + subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) + + def connectAck = subscriber.readNext() as ConnectAckInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + when: + + subscriber.send(new Subscribe311OutPacket( + Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), + 1 + )) + + def subscribeAck = subscriber.readNext() as SubscribeAckInPacket + + then: + subscribeAck.reasonCodes.stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) + when: + + publisher.publishWith() + .topic("test/retry/$subscriberId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + + def receivedPublish = subscriber.readNext() as PublishInPacket + + then: + receivedPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + def receivedDupPublish = subscriber.readNext() as PublishInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + receivedDupPublish = subscriber.readNext() as PublishInPacket + + subscriber.send(new PublishReceived311OutPacket(receivedDupPublish.getPacketId())) + def releaseAck = subscriber.readNext() as PublishReleaseInPacket + + subscriber.send(new PublishComplete311OutPacket(receivedDupPublish.getPacketId())) + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + releaseAck.packetId == receivedPublish.packetId + cleanup: + subscriber.close() + publisher.disconnect().join() + } + + def "mqtt 5 client should be generate session with one pending QoS 2 packet"() { + given: + def publisher = buildExternalMqtt5Client() + def subscriber = buildMqtt5MockClient() + def subscriberId = generateClientId() + when: + + publisher.connect().join() + + subscriber.connect() + subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) + + def connectAck = subscriber.readNext() as ConnectAckInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + when: + + subscriber.send(new Subscribe5OutPacket( + Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), + 1 + )) + + def subscribeAck = subscriber.readNext() as SubscribeAckInPacket + + then: + subscribeAck.reasonCodes.stream() + .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) + when: + + publisher.publishWith() + .topic("test/retry/$subscriberId") + .qos(MqttQos.AT_MOST_ONCE) + .payload(publishPayload) + .send() + .join() + + def receivedPublish = subscriber.readNext() as PublishInPacket + + then: + receivedPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + def receivedDupPublish = subscriber.readNext() as PublishInPacket + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + when: + + subscriber.close() + + subscriber.connect() + subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) + + connectAck = subscriber.readNext() as ConnectAckInPacket + receivedDupPublish = subscriber.readNext() as PublishInPacket + + subscriber.send(new PublishReceived5OutPacket( + receivedDupPublish.getPacketId(), + PublishReceivedReasonCode.SUCCESS + )) + + def releaseAck = subscriber.readNext() as PublishReleaseInPacket + + subscriber.send(new PublishComplete5OutPacket( + receivedDupPublish.getPacketId(), + PublishCompletedReasonCode.SUCCESS + )) + + then: + connectAck.reasonCode == ConnectAckReasonCode.SUCCESS + receivedDupPublish.duplicate + receivedDupPublish.packetId == receivedPublish.packetId + receivedDupPublish.payload == publishPayload + releaseAck.packetId == receivedPublish.packetId + cleanup: + subscriber.close() + publisher.disconnect().join() + } +} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/config/MqttBrokerTestConfig.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/config/MqttBrokerTestConfig.groovy similarity index 86% rename from src/test/groovy/com/ss/mqtt/broker/test/integration/config/MqttBrokerTestConfig.groovy rename to legacy/src/test/groovy/javasabr/mqtt/legacy/integration/config/MqttBrokerTestConfig.groovy index fd669c29..46a4501f 100644 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/config/MqttBrokerTestConfig.groovy +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/config/MqttBrokerTestConfig.groovy @@ -1,8 +1,8 @@ -package com.ss.mqtt.broker.test.integration.config +package javasabr.mqtt.legacy.integration.config -import com.ss.mqtt.broker.config.MqttBrokerConfig -import com.ss.mqtt.broker.config.MqttNetworkConfig -import com.ss.mqtt.broker.network.MqttConnection +import javasabr.mqtt.legacy.config.MqttBrokerConfig +import javasabr.mqtt.legacy.config.MqttNetworkConfig +import javasabr.mqtt.legacy.network.MqttConnection import javasabr.rlib.network.server.ServerNetwork import org.springframework.beans.factory.annotation.Qualifier import org.springframework.context.annotation.Bean diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/ClientIdRegistryTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/ClientIdRegistryTest.groovy new file mode 100644 index 00000000..d39280b0 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/ClientIdRegistryTest.groovy @@ -0,0 +1,132 @@ +package javasabr.mqtt.legacy.integration.service + +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import javasabr.mqtt.legacy.service.ClientIdRegistry +import javasabr.mqtt.legacy.integration.IntegrationSpecification +import javasabr.rlib.common.util.StringUtils +import org.springframework.beans.factory.annotation.Autowired + +class ClientIdRegistryTest extends IntegrationSpecification { + + @Autowired + ClientIdRegistry clientIdRegistry + + def "should register new client ids"() { + + given: + def clientId1 = "testClientId1" + def clientId2 = "testClientId2" + when: + def result1 = clientIdRegistry.register(clientId1).block() + def result2 = clientIdRegistry.register(clientId2).block() + then: + result1 && result2 + cleanup: + clientIdRegistry.unregister(clientId1).block() + clientIdRegistry.unregister(clientId2).block() + } + + def "should not register duplicated client ids"() { + + given: + + def clientId1 = "testClientId3" + def clientId2 = "testClientId4" + + clientIdRegistry.register(clientId1).block() + clientIdRegistry.register(clientId2).block() + + when: + def result1 = clientIdRegistry.register(clientId1).block() + def result2 = clientIdRegistry.register(clientId2).block() + then: + !result1 && !result2 + cleanup: + clientIdRegistry.unregister(clientId1).block() + clientIdRegistry.unregister(clientId2).block() + } + + def "should unregister exist client ids"() { + + given: + + def clientId1 = "testClientId5" + def clientId2 = "testClientId6" + + clientIdRegistry.register(clientId1).block() + clientIdRegistry.register(clientId2).block() + + when: + def result1 = clientIdRegistry.unregister(clientId1).block() + def result2 = clientIdRegistry.unregister(clientId2).block() + then: + result1 && result2 + } + + def "should not unregister not exist client ids"() { + + given: + def clientId1 = "testClientId7" + def clientId2 = "testClientId8" + when: + def result1 = clientIdRegistry.unregister(clientId1).block() + def result2 = clientIdRegistry.unregister(clientId2).block() + then: + !result1 && !result2 + } + + def "should generate and register new client ids"() { + + given: + def clientId1 = clientIdRegistry.generate().block() + def clientId2 = clientIdRegistry.generate().block() + when: + def result1 = clientIdRegistry.register(clientId1).block() + def result2 = clientIdRegistry.register(clientId2).block() + then: + result1 && result2 + StringUtils.isNotEmpty(clientId1) + StringUtils.isNotEmpty(clientId2) + cleanup: + clientIdRegistry.unregister(clientId1).block() + clientIdRegistry.unregister(clientId2).block() + } + + def "should generate invalid client ids"() { + + given: + def clientId1 = "testClientId*^&%" + def clientId2 = "testClientId{}@!" + def clientId3 = "testClientId9" + when: + def result1 = clientIdRegistry.validate(clientId1) + def result2 = clientIdRegistry.validate(clientId2) + def result3 = clientIdRegistry.validate(clientId3) + then: + !result1 && !result2 && result3 + } + + def "subscriber should register its client id on connect and unregister on disconnect"() { + given: + def clientId = clientIdRegistry.generate().block() + def client = buildExternalMqtt5Client(clientId) + when: + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + !clientIdRegistry.register(clientId).block() + when: + client.disconnect().join() + Thread.sleep(100) + then: + clientIdRegistry.register(clientId).block() + clientIdRegistry.unregister(clientId).block() + when: + result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + !clientIdRegistry.register(clientId).block() + cleanup: + client.disconnect().join() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy new file mode 100644 index 00000000..61bbfc27 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy @@ -0,0 +1,49 @@ +package javasabr.mqtt.legacy.integration.service + +import com.hivemq.client.mqtt.MqttClientState +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5SubAckException +import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode +import javasabr.mqtt.legacy.integration.IntegrationSpecification +import org.springframework.test.context.TestPropertySource +import spock.lang.Unroll + +import java.util.concurrent.CompletionException + +import static com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED +import static com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + +@TestPropertySource(locations = "classpath:disabled-features.properties") +class DisabledFeaturesSubscribtionServiceTest extends IntegrationSpecification { + + @Unroll + def "should reject subscribe with wrong topic filter"( + String wrongTopicFilter, + Mqtt5SubAckReasonCode reasonCode + ) { + given: + def subscriber = buildExternalMqtt5Client() + when: + subscriber.connectWith() + .send() + .join() + subscriber.subscribeWith() + .topicFilter(wrongTopicFilter) + .send() + .join() + then: + Thread.sleep(10) + subscriber.state == MqttClientState.DISCONNECTED + def ex = thrown CompletionException + if (ex.cause != null) { + ex.cause.class == Mqtt5SubAckException + ex.cause.message == "SUBACK contains only Error Codes" + ((Mqtt5SubAckException) ex.cause).mqttMessage.reasonCodes.contains(reasonCode) + } + where: + wrongTopicFilter | reasonCode + "topic/+" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + "topic/#" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + "topic/+/Filter" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED + "\$share/group/topic/Filter" | SHARED_SUBSCRIPTIONS_NOT_SUPPORTED + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/MqttSessionServiceTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/MqttSessionServiceTest.groovy new file mode 100644 index 00000000..6293bd83 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/MqttSessionServiceTest.groovy @@ -0,0 +1,47 @@ +package javasabr.mqtt.legacy.integration.service + +import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode +import javasabr.mqtt.legacy.service.ClientIdRegistry +import javasabr.mqtt.legacy.service.MqttSessionService +import javasabr.mqtt.legacy.integration.IntegrationSpecification +import org.springframework.beans.factory.annotation.Autowired + +class MqttSessionServiceTest extends IntegrationSpecification { + + @Autowired + ClientIdRegistry clientIdRegistry + + @Autowired + MqttSessionService mqttSessionService + + def "subscriber should create and re-use mqtt session"() { + given: + def clientId = clientIdRegistry.generate().block() + def client = buildExternalMqtt5Client(clientId) + when: + def shouldNoSession = mqttSessionService.restore(clientId).block() + def result = client.connect().join() + then: + result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS + shouldNoSession == null + mqttSessionService.restore(clientId).block() == null + when: + client.disconnect().join() + Thread.sleep(100) + def restored = mqttSessionService.restore(clientId).block() + then: + restored != null + when: + mqttSessionService.store(clientId, restored, externalConnectionConfig.getDefaultSessionExpiryInterval()).block() + client.connect().join() + shouldNoSession = mqttSessionService.restore(clientId).block() + then: + shouldNoSession == null + when: + client.disconnect().join() + Thread.sleep(100) + restored = mqttSessionService.restore(clientId).block() + then: + restored != null + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/SubscribtionServiceTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/SubscribtionServiceTest.groovy new file mode 100644 index 00000000..fb1f115d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/integration/service/SubscribtionServiceTest.groovy @@ -0,0 +1,207 @@ +package javasabr.mqtt.legacy.integration.service + +import com.hivemq.client.mqtt.datatypes.MqttQos +import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5SubAckException +import javasabr.mqtt.legacy.service.ClientIdRegistry +import javasabr.mqtt.legacy.service.impl.SimpleSubscriptionService +import javasabr.mqtt.legacy.integration.IntegrationSpecification +import javasabr.mqtt.model.ActionResult +import javasabr.mqtt.model.subscriber.SingleSubscriber +import org.spockframework.util.Pair +import org.springframework.beans.factory.annotation.Autowired +import spock.lang.Unroll + +import java.util.concurrent.CompletionException + +import static com.hivemq.client.mqtt.datatypes.MqttQos.* +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName +import static org.spockframework.util.Pair.of + +class SubscribtionServiceTest extends IntegrationSpecification { + + @Autowired + ClientIdRegistry clientIdRegistry + + @Autowired + SimpleSubscriptionService subscriptionService + + def "should clear/restore topic subscribers after disconnect/reconnect"() { + given: + def subscriber = buildExternalMqtt5Client(clientId) + def topicName = buildTopicName(topicFilter) + + def matchesCount = 0 + SingleSubscriber matchedSubscriber = null + def action = { subs, empty -> + matchesCount++ + matchedSubscriber = subs + ActionResult.SUCCESS + } + when: + subscriber.connectWith() + .cleanStart(true) + .send() + .join() + subscriber.subscribeWith() + .topicFilter(topicFilter) + .qos(AT_MOST_ONCE) + .send() + .join() + + def actionResult = subscriptionService.forEachTopicSubscriber(topicName, null, action) + then: + matchesCount == 1 + matchedSubscriber.user.getClientId() == clientId + matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter + actionResult == ActionResult.SUCCESS + when: + subscriber.disconnect().join() + subscriber.connectWith() + .cleanStart(false) + .send() + .join() + + actionResult = subscriptionService.forEachTopicSubscriber(topicName, clientId, action) + then: + matchesCount == 2 + matchedSubscriber.user.getClientId() == clientId + matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter + actionResult == ActionResult.SUCCESS + cleanup: + subscriber.disconnect().join() + } + + @Unroll + def "should match subscriber with the highest QoS"( + String topicName, + Pair topicFilter1, + Pair topicFilter2, + String targetTopicFilter + ) { + given: + def subscriber = buildExternalMqtt5Client() + + def matchesCount = 0 + SingleSubscriber matchedSubscriber = null + def action = { subs, empty -> + matchesCount++ + matchedSubscriber = subs + ActionResult.SUCCESS + } + subscriber.connectWith() + .send() + .join() + subscriber.subscribeWith() + .topicFilter(topicFilter1.first()) + .qos(topicFilter1.second()) + .send() + .join() + subscriber.subscribeWith() + .topicFilter(topicFilter2.first()) + .qos(topicFilter2.second()) + .send() + .join() + when: + subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), null, action) + then: + matchesCount == 1 + matchedSubscriber.subscribe.topicFilter.getRawTopic() == targetTopicFilter + cleanup: + subscriber.disconnect().join() + where: + topicName | topicFilter1 | topicFilter2 | targetTopicFilter + "topic/Filter" | of("topic/Filter", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" + "topic/Filter" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" + "topic/Another" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" + "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" + "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" + } + + @Unroll + def "should match all subscribers with shared and single topic"( + String topicName, + Pair topicFilter1, + Pair topicFilter2, + String targetTopicFilter, + int targetCount + ) { + given: + def clientId1 = clientIdRegistry.generate().block() + def clientId2 = clientIdRegistry.generate().block() + def subscriber1 = buildExternalMqtt5Client(clientId1) + def subscriber2 = buildExternalMqtt5Client(clientId2) + + def matchesCount = 0 + def matchedSubscribers = new LinkedHashSet() + def action = { SingleSubscriber subscriber, String clientId -> + matchesCount++ + matchedSubscribers.add(subscriber.user.clientId) + ActionResult.SUCCESS + } + + subscriber1.connectWith() + .send() + .join() + subscriber2.connectWith() + .send() + .join() + + subscriber1.subscribeWith() + .topicFilter(topicFilter1.first()) + .qos(topicFilter1.second()) + .send() + .join() + subscriber2.subscribeWith() + .topicFilter(topicFilter2.first()) + .qos(topicFilter2.second()) + .send() + .join() + when: + subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), clientId, action) + then: + matchesCount == targetCount + matchedSubscribers[0] == clientId1 + matchedSubscribers[1] == clientId2 + cleanup: + subscriber1.disconnect().join() + subscriber2.disconnect().join() + where: + topicName | topicFilter1 | topicFilter2 | targetTopicFilter | targetCount + "topic/Filter" | of("\$share/group1/topic/Filter", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 + "topic/Filter" | of("\$share/group1/topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" | 2 + "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 + "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" | 2 + } + + @Unroll + def "should reject subscribe with wrong topic filter"( + String wrongTopicFilter, + Class exception + ) { + given: + def subscriber = buildExternalMqtt5Client() + when: + subscriber.connectWith() + .send() + .join() + subscriber.subscribeWith() + .topicFilter(wrongTopicFilter) + .send() + .join() + then: + def ex = thrown exception + if (ex.cause != null) { + ex.cause.class == Mqtt5SubAckException + ex.cause.message == "SUBACK contains only Error Codes" + } + cleanup: + subscriber.disconnect().join() + where: + wrongTopicFilter | exception + "topic/" | CompletionException + "topic//Filter" | CompletionException + "/topic/Another" | CompletionException + "topic/##" | IllegalArgumentException + "++/Filter/First" | IllegalArgumentException + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/mock/MqttMockClient.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/mock/MqttMockClient.groovy new file mode 100644 index 00000000..22fcf34b --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/mock/MqttMockClient.groovy @@ -0,0 +1,124 @@ +package javasabr.mqtt.legacy.mock + +import javasabr.mqtt.legacy.network.MqttConnection +import javasabr.mqtt.legacy.network.packet.PacketType +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.legacy.network.packet.in.MqttReadablePacket +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.MqttWritablePacket +import javasabr.mqtt.legacy.util.MqttDataUtils +import javasabr.rlib.common.util.NumberUtils + +import java.nio.ByteBuffer + +class MqttMockClient { + + private final ByteBuffer received = ByteBuffer.allocate(1024).clear() + + private final String brokerHost + private final int brokerPort + private final MqttConnection connection + + private Socket socket + + MqttMockClient(String brokerHost, int brokerPort, MqttConnection connection) { + this.brokerHost = brokerHost + this.brokerPort = brokerPort + this.connection = connection + } + + void connect() { + + if (socket != null) { + return + } + + socket = new Socket(brokerHost, brokerPort) + } + + void send(MqttWritablePacket packet) { + + def dataBuffer = ByteBuffer.allocate(1024) + + packet.write(dataBuffer) + + dataBuffer.flip() + + def finalBuffer = ByteBuffer.allocate(1024) + finalBuffer.put((byte) packet.getPacketTypeAndFlags()) + + MqttDataUtils.writeMbi(dataBuffer.remaining(), finalBuffer) + + finalBuffer.put(dataBuffer).flip() + + def out = socket.getOutputStream() + out.write(finalBuffer.array(), 0, finalBuffer.remaining()) + + Thread.sleep(50) + } + + MqttReadablePacket readNext() { + + if (received.position() == 0) { + + def input = socket.getInputStream() + def readBytes = input.read(received.array(), received.position(), received.capacity() - received.position()) + + if (readBytes > 0) { + received.position(received.position() + readBytes) + } + } + + received.flip() + + if (!received.hasRemaining()) { + throw new IllegalStateException("No received bytes.") + } + + def startByte = Byte.toUnsignedInt(received.get()) + def type = NumberUtils.getHighByteBits(startByte) + def info = NumberUtils.getLowByteBits(startByte) + def dataSize = MqttDataUtils.readMbi(received) + + MqttReadablePacket packet + + switch (PacketType.fromByte(type)) { + case PacketType.CONNECT_ACK: + packet = new ConnectAckInPacket(info) + break + case PacketType.SUBSCRIBE_ACK: + packet = new SubscribeAckInPacket(info) + break + case PacketType.PUBLISH: + packet = new PublishInPacket(info) + break + case PacketType.PUBLISH_RELEASED: + packet = new PublishReleaseInPacket(info) + break + default: + throw new IllegalStateException("Unknown packet of type: $type") + } + + packet.read(connection, received, dataSize) + + if (received.hasRemaining()) { + received.compact() + } else { + received.clear() + } + + return packet + } + + def close() { + + if (socket != null) { + socket.close() + socket = null + received.clear() + Thread.sleep(50) + } + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/model/ActionResultTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/ActionResultTest.groovy new file mode 100644 index 00000000..3c93191d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/ActionResultTest.groovy @@ -0,0 +1,34 @@ +package javasabr.mqtt.legacy.model + +import javasabr.mqtt.legacy.network.NetworkUnitSpecification +import javasabr.mqtt.model.ActionResult +import spock.lang.Unroll + +import static javasabr.mqtt.model.ActionResult.EMPTY +import static javasabr.mqtt.model.ActionResult.FAILED +import static javasabr.mqtt.model.ActionResult.SUCCESS + +class ActionResultTest extends NetworkUnitSpecification { + + @Unroll + def "#first and #second == #result"( + ActionResult first, + ActionResult second, + ActionResult result + ) { + expect: + first.and(second) == result + where: + first | second | result + SUCCESS | SUCCESS | SUCCESS + SUCCESS | FAILED | FAILED + FAILED | SUCCESS | FAILED + FAILED | FAILED | FAILED + EMPTY | EMPTY | EMPTY + EMPTY | FAILED | FAILED + FAILED | EMPTY | FAILED + SUCCESS | EMPTY | SUCCESS + EMPTY | SUCCESS | SUCCESS + } + +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicSubscriberTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicSubscriberTest.groovy new file mode 100644 index 00000000..1c3f571d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicSubscriberTest.groovy @@ -0,0 +1,76 @@ +package javasabr.mqtt.legacy.model + +import javasabr.mqtt.legacy.network.MqttClient +import javasabr.mqtt.legacy.network.NetworkUnitSpecification +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.mqtt.model.topic.TopicFilter +import javasabr.mqtt.model.topic.TopicName +import javasabr.mqtt.model.topic.TopicSubscribers +import spock.lang.Unroll + +import static javasabr.mqtt.model.QoS.AT_LEAST_ONCE +import static javasabr.mqtt.model.QoS.AT_MOST_ONCE +import static javasabr.mqtt.model.QoS.EXACTLY_ONCE +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName + +class TopicSubscriberTest extends NetworkUnitSpecification { + + @Unroll + def "should choose #matchedQos from #subscriberQos"( + TopicFilter[] topicFilters, + TopicName topicNames, + QoS[] subscriberQos, + QoS[] matchedQos, + MqttClient[] mqttClients + ) { + given: + def subscribeTopicFilter = Mock(SubscribeTopicFilter) { + getQos() >>> subscriberQos + getTopicFilter() >>> topicFilters + } + def topicSubscriber = new TopicSubscribers() + when: + topicSubscriber.addSubscriber(mqttClients[0], subscribeTopicFilter) + topicSubscriber.addSubscriber(mqttClients[1], subscribeTopicFilter) + topicSubscriber.addSubscriber(mqttClients[2], subscribeTopicFilter) + then: + def subscribers = topicSubscriber.matches(topicNames) + subscribers.size() == matchedQos.size() + for (int i = 0; i < subscribers.size(); i++) { + subscribers[i].qos == matchedQos[i] + } + where: + topicFilters << [ + [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")], + [buildTopicFilter("topic/+/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], + [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], + [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")] + ] + topicNames << [ + buildTopicName("topic/second/in"), + buildTopicName("topic/first/in"), + buildTopicName("topic/second/in"), + buildTopicName("topic/second/in") + ] + subscriberQos << [ + [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], + [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], + [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], + [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] + ] + matchedQos << [ + [EXACTLY_ONCE], + [AT_MOST_ONCE], + [AT_LEAST_ONCE], + [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] + ] + mqttClients << [ + [defaultMqttClient, defaultMqttClient, defaultMqttClient], + [defaultMqttClient, defaultMqttClient, defaultMqttClient], + [defaultMqttClient, defaultMqttClient, defaultMqttClient], + [defaultMqttClient(), defaultMqttClient(), defaultMqttClient()] + ] + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicTest.groovy new file mode 100644 index 00000000..edb40056 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/model/TopicTest.groovy @@ -0,0 +1,108 @@ +package javasabr.mqtt.legacy.model + +import javasabr.mqtt.model.MqttUser +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.mqtt.model.topic.TopicSubscribers +import spock.lang.Specification +import spock.lang.Unroll + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName +import static javasabr.mqtt.model.utils.TopicUtils.isInvalid + +class TopicTest extends Specification { + + @Unroll + def "should create topic name: [#stringTopicName]"() { + when: + def topicName = buildTopicName(stringTopicName) + then: + topicName.segments.size() == levelsCount + topicName.rawTopic == stringTopicName + topicName.length == stringTopicName.length() + where: + stringTopicName | levelsCount + "topic/second/in" | 3 + "topic/second" | 2 + } + + @Unroll + def "should fail create topic name: [#stringTopicName]"() { + expect: + isInvalid(buildTopicName(stringTopicName)) + where: + stringTopicName << [ + "", + "topic/+", + "topic/#" + ] + } + + @Unroll + def "should create topic filter: [#stringTopicFilter]"() { + when: + def topicFilter = buildTopicFilter(stringTopicFilter) + then: + topicFilter.segments.size() == levelsCount + topicFilter.rawTopic == stringTopicFilter + topicFilter.length == stringTopicFilter.length() + where: + stringTopicFilter | levelsCount + "topic/in" | 2 + "topic/+" | 2 + "topic/#" | 2 + "topic/+/in" | 3 + } + + @Unroll + def "should fail create topic filter: [#stringTopicFilter]"() { + expect: + isInvalid(buildTopicFilter(stringTopicFilter)) + where: + stringTopicFilter << [ + "", + "topic/in/", + "/topic/in", + "topic//in", + "topic/++/in", + "topic/#/in", + "topic/##" + ] + } + + @Unroll + def "should match topic filter: [#topicFilter] with topic name: [#topicName]"() { + expect: + def builtTopicName = buildTopicName(topicName) + def builtTopicFilter = buildTopicFilter(topicFilter) + def subscribers = new TopicSubscribers() + subscribers.addSubscriber(Mock(MqttUser), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) + subscribers.matches(builtTopicName) + where: + topicFilter | topicName + "topic/in" | "topic/in" + "topic/+" | "topic/in" + "topic/#" | "topic/in" + "topic/+/in" | "topic/m/in" + } + + @Unroll + def "should not match topic filter: [#topicFilter] with topic name: [#topicName]"() { + expect: + def builtTopicName = buildTopicName(topicName) + def builtTopicFilter = buildTopicFilter(topicFilter) + def subscribers = new TopicSubscribers() + subscribers.addSubscriber(Mock(MqttUser), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) + !subscribers.matches(builtTopicName) + where: + topicFilter | topicName + "topic/in" | "topic/m/in" + // "topic/in" | "topic/in/m" + // "topic/+" | "topic/m/in" + // "topic/+" | "topic/in/m" + "topic/#" | "topic" + "topic/+/in" | "topic/m/n" + "topic/+/in" | "topic/in" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/NetworkUnitSpecification.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/NetworkUnitSpecification.groovy new file mode 100644 index 00000000..d63998d5 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/NetworkUnitSpecification.groovy @@ -0,0 +1,233 @@ +package javasabr.mqtt.legacy.network + +import javasabr.mqtt.legacy.UnitSpecification +import javasabr.mqtt.model.MqttConnectionConfig +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.mqtt.model.data.type.StringPair +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter +import javasabr.rlib.collections.array.Array +import javasabr.rlib.collections.array.IntArray +import spock.lang.Shared + +import java.nio.charset.StandardCharsets + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName + + +class NetworkUnitSpecification extends UnitSpecification { + + public static final keepAliveEnabled = true + public static final sessionsEnabled = true + public static final retainAvailable = true + public static final sharedSubscriptionAvailable = true + public static final wildcardSubscriptionAvailable = true + public static final subscriptionIdAvailable = true + + public static final maxQos = QoS.AT_MOST_ONCE + public static final sessionPresent = true + public static final cleanStart = false + public static final willRetain = false + public static final clientId = "testClientId" + public static final packetId = 1234 as short + public static final userName = "testUser" + public static final userPassword = "testPassword".getBytes(StandardCharsets.UTF_8) + public static final keepAlive = 120 + public static final sessionExpiryInterval = 300 + public static final messageExpiryInterval = 60 + public static final topicAlias = 252 + public static final receiveMaximum = 10 + public static final maximumPacketSize = 1024 + public static final topicAliasMaximum = 32 + public static final subscriptionId = 637 + public static final subscriptionId2 = 623 + public static final serverKeepAlive = 1200 + public static final requestResponseInformation = true + public static final requestProblemInformation = true + public static final responseInformation = "responseInformation" + public static final authMethod = "testAuthMethod" + public static final authData = "testAuthData".getBytes(StandardCharsets.UTF_8) + public static final reasonString = "reasonString" + public static final publishTopic = buildTopicName("publish/Topic") + public static final responseTopic = "response/Topic" + public static final topicFilter = "topic/Filter" + public static final topicFilter1Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter), QoS.AT_LEAST_ONCE) + public static final topicFilter1Obj5 = new SubscribeTopicFilter( + buildTopicFilter(topicFilter), + QoS.AT_LEAST_ONCE, + SubscribeRetainHandling.DO_NOT_SEND, + true, + false, + ) + public static final topicFilter2 = "topic/Filter2" + public static final topicFilter2Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter2), QoS.EXACTLY_ONCE) + public static final topicFilter2Obj5 = new SubscribeTopicFilter( + buildTopicFilter(topicFilter2), + QoS.EXACTLY_ONCE, + SubscribeRetainHandling.DO_NOT_SEND, + true, + false, + ) + public static final serverReference = "serverReference" + public static final contentType = "application/json" + public static final subscribeAckReasonCodes = Array.typed( + SubscribeAckReasonCode, + SubscribeAckReasonCode.GRANTED_QOS_1, + SubscribeAckReasonCode.GRANTED_QOS_0, + SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + ) + public static final unsubscribeAckReasonCodes = Array.typed( + UnsubscribeAckReasonCode, + UnsubscribeAckReasonCode.SUCCESS, + UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR, + UnsubscribeAckReasonCode.UNSPECIFIED_ERROR + ) + public static final userProperties = Array.typed( + StringPair, + new StringPair("key1", "val1"), + new StringPair("key2", "val2"), + new StringPair("key3", "val3"), + ) + public static final subscriptionIds = IntArray.of(subscriptionId, subscriptionId2) + public static final topicFilters = Array.of(topicFilter, topicFilter2) + public static final topicFiltersObj311 = Array.of(topicFilter1Obj311, topicFilter2Obj311) + public static final topicFiltersObj5 = Array.of(topicFilter1Obj5, topicFilter2Obj5) + public static final publishPayload = "publishPayload".getBytes(StandardCharsets.UTF_8) + public static final correlationData = "correlationData".getBytes(StandardCharsets.UTF_8) + + @Shared + MqttClient defaultMqttClient = defaultMqttClient() + + @Shared + MqttConnectionConfig mqttConnectionConfig = defaultMqttConnectionConfig() + + @Shared + MqttConnection mqtt5Connection = defaultMqttConnection(MqttVersion.MQTT_5) + + @Shared + MqttConnection mqtt311Connection = defaultMqttConnection(MqttVersion.MQTT_3_1_1) + + MqttClient defaultMqttClient() { + return mqttClient( + mqttConnectionConfig, + sessionExpiryInterval, + receiveMaximum, + maximumPacketSize, + clientId, + serverKeepAlive, + topicAliasMaximum + ) + } + + MqttConnectionConfig defaultMqttConnectionConfig() { + return mqttConnectionConfig( + maxQos, + maximumPacketSize, + serverKeepAlive, + receiveMaximum, + topicAliasMaximum, + sessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable + ) + } + + + MqttConnection defaultMqttConnection(MqttVersion mqttVersion) { + return mqttConnection( + mqttVersion, + mqttConnectionConfig, + sessionExpiryInterval, + receiveMaximum, + maximumPacketSize, + clientId, + serverKeepAlive, + topicAliasMaximum + ) + } + + + static MqttConnectionConfig mqttConnectionConfig( + QoS maxQos, + int maximumPacketSize, + int serverKeepAlive, + int receiveMaximum, + int topicAliasMaximum, + long sessionExpiryInterval, + boolean keepAliveEnabled, + boolean sessionsEnabled, + boolean retainAvailable, + boolean wildcardSubscriptionAvailable, + boolean subscriptionIdAvailable, + boolean sharedSubscriptionAvailable + + ) { + return new MqttConnectionConfig( + maxQos, + maximumPacketSize, + serverKeepAlive, + receiveMaximum, + topicAliasMaximum, + sessionExpiryInterval, + keepAliveEnabled, + sessionsEnabled, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable + ) + } + + MqttConnection mqttConnection( + MqttVersion mqttVersion, + MqttConnectionConfig mqttConnectionConfig, + long sessionExpiryInterval, + int receiveMaximum, + int maximumPacketSize, + String clientId, + int serverKeepAlive, + int topicAliasMaximum + ) { + return Stub(MqttConnection) { + isSupported(_ as MqttVersion) >> { MqttVersion version -> mqttVersion >= version } + getConfig() >> mqttConnectionConfig + getClient() >> mqttClient( + mqttConnectionConfig, + sessionExpiryInterval, + receiveMaximum, + maximumPacketSize, + clientId, + serverKeepAlive, + topicAliasMaximum + ) + } + } + + MqttClient mqttClient( + MqttConnectionConfig mqttConnectionConfig, + long sessionExpiryInterval, + int receiveMaximum, + int maximumPacketSize, + String clientId, + int serverKeepAlive, + int topicAliasMaximum + ) { + return Stub(MqttClient.UnsafeMqttClient) { + getConnectionConfig() >> mqttConnectionConfig + getSessionExpiryInterval() >> sessionExpiryInterval + getReceiveMax() >> receiveMaximum + getMaximumPacketSize() >> maximumPacketSize + getClientId() >> clientId + getKeepAlive() >> serverKeepAlive + getTopicAliasMaximum() >> topicAliasMaximum + } + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/AuthenticationInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/AuthenticationInPacketTest.groovy new file mode 100644 index 00000000..ce2f66df --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/AuthenticationInPacketTest.groovy @@ -0,0 +1,86 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.AuthenticationInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class AuthenticationInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.put(AuthenticateReasonCode.SUCCESS.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new AuthenticationInPacket(0b1111_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reasonCode == AuthenticateReasonCode.SUCCESS + packet.authenticationMethod == authMethod + packet.authenticationData == authData + packet.reason == reasonString + packet.userProperties == userProperties + when: + + propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + } + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + packet = new AuthenticationInPacket(0b1111_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + packet.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION + packet.authenticationMethod == authMethod + packet.authenticationData == authData + packet.reason == reasonString + packet.userProperties == userProperties + when: + + propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + } + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + packet = new AuthenticationInPacket(0b1111_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION + packet.authenticationMethod == authMethod + packet.authenticationData == authData + packet.reason == "" + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/BaseInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/BaseInPacketTest.groovy new file mode 100644 index 00000000..577efe68 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/BaseInPacketTest.groovy @@ -0,0 +1,6 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.NetworkUnitSpecification + +class BaseInPacketTest extends NetworkUnitSpecification { +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectAckInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectAckInPacketTest.groovy new file mode 100644 index 00000000..8fcb0703 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectAckInPacketTest.groovy @@ -0,0 +1,126 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class ConnectAckInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putBoolean(sessionPresent) + it.put(ConnectAckReasonCode.NOT_AUTHORIZED.mqtt311) + } + + when: + def packet = new ConnectAckInPacket(0b0010_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reasonCode == ConnectAckReasonCode.NOT_AUTHORIZED + packet.sessionPresent == sessionPresent + packet.serverReference == "" + packet.reason == "" + packet.assignedClientId == "" + packet.authenticationData == ArrayUtils.EMPTY_BYTE_ARRAY + packet.authenticationMethod == "" + packet.maximumQos == QoS.EXACTLY_ONCE + packet.retainAvailable == MqttProperties.RETAIN_AVAILABLE_DEFAULT + packet.sharedSubscriptionAvailable == MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT + packet.wildcardSubscriptionAvailable == MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT + packet.subscriptionIdAvailable == MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT + packet.responseInformation == "" + packet.maximumPacketSize == MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED + packet.serverKeepAlive == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED + packet.sessionExpiryInterval == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED + packet.topicAliasMaximum == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED + packet.receiveMax == MqttProperties.RECEIVE_MAXIMUM_UNDEFINED + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) + it.putProperty(PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, clientId) + it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maximumPacketSize) + it.putProperty(PacketProperty.MAXIMUM_QOS, QoS.AT_LEAST_ONCE.ordinal()) + it.putProperty(PacketProperty.RECEIVE_MAXIMUM, receiveMaximum) + it.putProperty(PacketProperty.RETAIN_AVAILABLE, retainAvailable) + it.putProperty(PacketProperty.RESPONSE_INFORMATION, responseInformation) + it.putProperty(PacketProperty.SERVER_KEEP_ALIVE, serverKeepAlive) + it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) + it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) + it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) + it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaximum) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putBoolean(sessionPresent) + it.put(ConnectAckReasonCode.PAYLOAD_FORMAT_INVALID.mqtt5) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new ConnectAckInPacket(0b0010_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reasonCode == ConnectAckReasonCode.PAYLOAD_FORMAT_INVALID + packet.sessionPresent == sessionPresent + packet.serverReference == serverReference + packet.reason == reasonString + packet.assignedClientId == clientId + packet.authenticationData == authData + packet.authenticationMethod == authMethod + packet.maximumPacketSize == maximumPacketSize + packet.maximumQos == QoS.AT_LEAST_ONCE + packet.receiveMax == receiveMaximum + packet.retainAvailable == retainAvailable + packet.responseInformation == responseInformation + packet.serverKeepAlive == serverKeepAlive + packet.sessionExpiryInterval == sessionExpiryInterval + packet.sharedSubscriptionAvailable == sharedSubscriptionAvailable + packet.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable + packet.subscriptionIdAvailable == subscriptionIdAvailable + packet.topicAliasMaximum == topicAliasMaximum + + when: + + propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) + it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) + it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) + } + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putBoolean(sessionPresent) + it.put(ConnectAckReasonCode.PACKET_TOO_LARGE.mqtt5) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + packet = new ConnectAckInPacket(0b0010_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reasonCode == ConnectAckReasonCode.PACKET_TOO_LARGE + packet.sharedSubscriptionAvailable == sharedSubscriptionAvailable + packet.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable + packet.subscriptionIdAvailable == subscriptionIdAvailable + + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectInPacketTest.groovy new file mode 100644 index 00000000..2eaf3704 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/ConnectInPacketTest.groovy @@ -0,0 +1,120 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.exception.MalformedPacketMqttException +import javasabr.mqtt.legacy.network.packet.in.ConnectInPacket +import javasabr.mqtt.model.MqttVersion +import javasabr.mqtt.model.PacketProperty +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class ConnectInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString("MQTT") + it.put(4 as byte) + it.put(0b11000010 as byte) + it.putShort(keepAlive as short) + it.putString(clientId) + it.putString(userName) + it.putBytes(userPassword) + } + + when: + def packet = new ConnectInPacket(0b0001_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.clientId == clientId + packet.mqttVersion == MqttVersion.MQTT_3_1_1 + packet.password == userPassword + packet.username == userName + packet.willTopic == "" + packet.willQos == 0 + packet.willPayload == ArrayUtils.EMPTY_BYTE_ARRAY + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(PacketProperty.RECEIVE_MAXIMUM, receiveMaximum) + it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maximumPacketSize) + it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaximum) + it.putProperty(PacketProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation ? 1 : 0) + it.putProperty(PacketProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation ? 1 : 0) + it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) + it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString("MQTT") + it.put(5 as byte) + it.put(0b11000010 as byte) + it.putShort(keepAlive as short) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.putString(clientId) + it.putString(userName) + it.putBytes(userPassword) + } + + when: + def packet = new ConnectInPacket(0b0001_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.keepAlive == keepAlive + packet.authenticationMethod == authMethod + packet.authenticationData == authData + packet.clientId == clientId + packet.mqttVersion == MqttVersion.MQTT_5 + packet.maximumPacketSize == maximumPacketSize + packet.password == userPassword + packet.username == userName + packet.topicAliasMaximum == topicAliasMaximum + packet.sessionExpiryInterval == sessionExpiryInterval + packet.receiveMax == receiveMaximum + packet.willTopic == "" + packet.willQos == 0 + packet.willPayload == ArrayUtils.EMPTY_BYTE_ARRAY + packet.userProperties == userProperties + } + + def "should not read packet correctly with invalid UTF8 strings"(byte[] stringBytes) { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString("MQTT") + it.put(5 as byte) + it.put(0b11000010 as byte) + it.putShort(keepAlive as short) + it.putMbi(0) + it.putBytes(stringBytes) + it.putString(userName) + it.putBytes(userPassword) + } + + when: + def packet = new ConnectInPacket(0b0001_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + !result + packet.exception instanceof MalformedPacketMqttException + where: + stringBytes << [ + // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt + [0xF4, 0x90, 0x80, 0x80] as byte[], + [0xFE, 0xFE, 0xFF, 0xFF] as byte[], + [0xFC, 0x80, 0x80, 0x80, 0x80, 0xAF] as byte[], + [0xED, 0xAF, 0xBF, 0xED, 0xBF, 0xBF] as byte[], + ] + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/DisconnectInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/DisconnectInPacketTest.groovy new file mode 100644 index 00000000..a0aae3e6 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/DisconnectInPacketTest.groovy @@ -0,0 +1,61 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.DisconnectInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.DisconnectReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class DisconnectInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.put(DisconnectReasonCode.QUOTA_EXCEEDED.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new DisconnectInPacket(0b1110_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.serverReference == serverReference + packet.reasonCode == DisconnectReasonCode.QUOTA_EXCEEDED + packet.sessionExpiryInterval == sessionExpiryInterval + packet.userProperties == userProperties + when: + + propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) + it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) + } + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.put(DisconnectReasonCode.PACKET_TOO_LARGE.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + packet = new DisconnectInPacket(0b1110_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.serverReference == serverReference + packet.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE + packet.sessionExpiryInterval == sessionExpiryInterval + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishAckInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishAckInPacketTest.groovy new file mode 100644 index 00000000..c68b0b36 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishAckInPacketTest.groovy @@ -0,0 +1,72 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.PublishAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishAckInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + } + + when: + def packet = new PublishAckInPacket(0b0100_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishAckReasonCode.SUCCESS + packet.userProperties == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishAckReasonCode.PAYLOAD_FORMAT_INVALID.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new PublishAckInPacket(0b0100_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCode == PublishAckReasonCode.PAYLOAD_FORMAT_INVALID + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishAckReasonCode.UNSPECIFIED_ERROR.value) + it.putMbi(0) + } + + packet = new PublishAckInPacket(0b0100_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishAckReasonCode.UNSPECIFIED_ERROR + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishCompleteInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishCompleteInPacketTest.groovy new file mode 100644 index 00000000..db0aa107 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishCompleteInPacketTest.groovy @@ -0,0 +1,72 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishCompleteInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + } + + when: + def packet = new PublishCompleteInPacket(0b0111_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishCompletedReasonCode.SUCCESS + packet.userProperties == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new PublishCompleteInPacket(0b0111_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) + it.putMbi(0) + } + + packet = new PublishCompleteInPacket(0b0111_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishInPacketTest.groovy new file mode 100644 index 00000000..5f211998 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishInPacketTest.groovy @@ -0,0 +1,113 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.data.type.StringPair +import javasabr.rlib.collections.array.Array +import javasabr.rlib.collections.array.IntArray +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class PublishInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString(publishTopic.toString()) + it.putShort(packetId) + it.put(publishPayload) + } + + when: + def packet = new PublishInPacket(0b0110_0011 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.qos == QoS.AT_LEAST_ONCE + !packet.duplicate + packet.retained + packet.responseTopic == "" + packet.subscriptionIds == IntArray.empty() + packet.contentType == "" + packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY + packet.payload == publishPayload + packet.packetId == packetId + packet.userProperties == Array.empty() + packet.messageExpiryInterval == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED + packet.topicAlias == MqttProperties.TOPIC_ALIAS_DEFAULT + packet.payloadFormatIndicator == MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.PAYLOAD_FORMAT_INDICATOR, 1) + it.putProperty(PacketProperty.MESSAGE_EXPIRY_INTERVAL, messageExpiryInterval) + it.putProperty(PacketProperty.TOPIC_ALIAS, topicAlias) + it.putProperty(PacketProperty.RESPONSE_TOPIC, responseTopic) + it.putProperty(PacketProperty.CORRELATION_DATA, correlationData) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionIds) + it.putProperty(PacketProperty.CONTENT_TYPE, contentType) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString(publishTopic.toString()) + it.putShort(packetId) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.put(publishPayload) + } + + when: + def packet = new PublishInPacket(0b0110_0011 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.qos == QoS.AT_LEAST_ONCE + !packet.duplicate + packet.retained + packet.responseTopic == responseTopic + packet.subscriptionIds == subscriptionIds + packet.contentType == contentType + packet.correlationData == correlationData + packet.payload == publishPayload + packet.packetId == packetId + packet.userProperties == userProperties + packet.messageExpiryInterval == messageExpiryInterval + packet.topicAlias == topicAlias + packet.payloadFormatIndicator + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putString(publishTopic.toString()) + it.putShort(packetId) + it.putMbi(0) + it.put(publishPayload) + } + + packet = new PublishInPacket(0b0110_0011 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.qos == QoS.AT_LEAST_ONCE + !packet.duplicate + packet.retained + packet.responseTopic == "" + packet.subscriptionIds == IntArray.empty() + packet.contentType == "" + packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY + packet.payload == publishPayload + packet.packetId == packetId + packet.userProperties == Array.empty(StringPair) + packet.messageExpiryInterval == MqttProperties.MESSAGE_EXPIRY_INTERVAL_UNDEFINED + packet.topicAlias == MqttProperties.TOPIC_ALIAS_DEFAULT + packet.payloadFormatIndicator == MqttProperties.PAYLOAD_FORMAT_INDICATOR_DEFAULT + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReceivedInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReceivedInPacketTest.groovy new file mode 100644 index 00000000..2936a8c4 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReceivedInPacketTest.groovy @@ -0,0 +1,72 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishReceivedInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + } + + when: + def packet = new PublishReceivedInPacket(0b0101_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishReceivedReasonCode.SUCCESS + packet.userProperties == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new PublishReceivedInPacket(0b0101_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCode == PublishReceivedReasonCode.QUOTA_EXCEEDED + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) + it.putMbi(0) + } + + packet = new PublishReceivedInPacket(0b0101_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReleaseInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReleaseInPacketTest.groovy new file mode 100644 index 00000000..2cae291a --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/PublishReleaseInPacketTest.groovy @@ -0,0 +1,72 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishReleaseInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + } + + when: + def packet = new PublishReleaseInPacket(0b0110_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishReleaseReasonCode.SUCCESS + packet.userProperties == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + } + + when: + def packet = new PublishReleaseInPacket(0b0110_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCode == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(PublishReleaseReasonCode.SUCCESS.value) + it.putMbi(0) + } + + packet = new PublishReleaseInPacket(0b0110_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCode == PublishReleaseReasonCode.SUCCESS + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeAckInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeAckInPacketTest.groovy new file mode 100644 index 00000000..867c023b --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeAckInPacketTest.groovy @@ -0,0 +1,93 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class SubscribeAckInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) + it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) + it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) + it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + } + + when: + def packet = new SubscribeAckInPacket(0b1001_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCodes.size() == 4 + packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 + packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 + packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) + it.put(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) + it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) + it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + } + + when: + def packet = new SubscribeAckInPacket(0b1001_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCodes.size() == 4 + packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 + packet.reasonCodes.get(1) == SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(0) + it.put(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED.value) + it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) + it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) + it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + } + + packet = new SubscribeAckInPacket(0b1001_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCodes.size() == 4 + packet.reasonCodes.get(0) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED + packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 + packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 + packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeInPacketTest.groovy new file mode 100644 index 00000000..c3b9f085 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/SubscribeInPacketTest.groovy @@ -0,0 +1,114 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.SubscribeInPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.QoS +import javasabr.mqtt.model.SubscribeRetainHandling +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class SubscribeInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putString(topicFilter) + it.put(0b0000_0001 as byte) + it.putString(topicFilter2) + it.put(0b0000_0010 as byte) + } + + when: + def packet = new SubscribeInPacket(0b1000_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE + packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter + packet.topicFilters.get(0).isNoLocal() + packet.topicFilters.get(0).isRetainAsPublished() + packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND + packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE + packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 + packet.topicFilters.get(1).isNoLocal() + packet.topicFilters.get(1).isRetainAsPublished() + packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND + packet.packetId == packetId + packet.userProperties == Array.empty() + packet.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.putString(topicFilter) + it.put(0b0000_1001 as byte) + it.putString(topicFilter2) + it.put(0b0001_0110 as byte) + } + + when: + def packet = new SubscribeInPacket(0b0110_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE + packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter + !packet.topicFilters.get(0).isNoLocal() + packet.topicFilters.get(0).isRetainAsPublished() + packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND + packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE + packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 + packet.topicFilters.get(1).isNoLocal() + !packet.topicFilters.get(1).isRetainAsPublished() + packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST + packet.packetId == packetId + packet.userProperties == userProperties + packet.subscriptionId == subscriptionId + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(0) + it.putString(topicFilter) + it.put(0b0000_0001 as byte) + it.putString(topicFilter2) + it.put(0b0000_0010 as byte) + } + + packet = new SubscribeInPacket(0b0110_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE + packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter + !packet.topicFilters.get(0).isNoLocal() + !packet.topicFilters.get(0).isRetainAsPublished() + packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND + packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE + packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 + !packet.topicFilters.get(1).isNoLocal() + !packet.topicFilters.get(1).isRetainAsPublished() + packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND + packet.packetId == packetId + packet.userProperties == Array.empty() + packet.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeAckInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeAckInPacketTest.groovy new file mode 100644 index 00000000..997b3de9 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeAckInPacketTest.groovy @@ -0,0 +1,81 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeAckInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class UnsubscribeAckInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + } + + when: + def packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCodes == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.REASON_STRING, reasonString) + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.put(UnsubscribeAckReasonCode.SUCCESS.value) + it.put(UnsubscribeAckReasonCode.SUCCESS.value) + it.put(UnsubscribeAckReasonCode.NOT_AUTHORIZED.value) + it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + } + + when: + def packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == reasonString + packet.packetId == packetId + packet.reasonCodes.size() == 4 + packet.reasonCodes.get(0) == UnsubscribeAckReasonCode.SUCCESS + packet.reasonCodes.get(1) == UnsubscribeAckReasonCode.SUCCESS + packet.reasonCodes.get(2) == UnsubscribeAckReasonCode.NOT_AUTHORIZED + packet.reasonCodes.get(3) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(0) + it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR.value) + it.put(UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) + } + + packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.reason == "" + packet.packetId == packetId + packet.reasonCodes.size() == 2 + packet.reasonCodes.get(0) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR + packet.reasonCodes.get(1) == UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeInPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeInPacketTest.groovy new file mode 100644 index 00000000..8e861c50 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/in/UnsubscribeInPacketTest.groovy @@ -0,0 +1,77 @@ +package javasabr.mqtt.legacy.network.in + +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeInPacket +import javasabr.mqtt.model.PacketProperty +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class UnsubscribeInPacketTest extends BaseInPacketTest { + + def "should read packet correctly as mqtt 3.1.1"() { + + given: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putString(topicFilter) + it.putString(topicFilter2) + } + + when: + def packet = new UnsubscribeInPacket(0b1011_0000 as byte) + def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).toString() == topicFilter + packet.topicFilters.get(1).toString() == topicFilter2 + packet.packetId == packetId + packet.userProperties == Array.empty() + } + + def "should read packet correctly as mqtt 5.0"() { + + given: + + def propertiesBuffer = BufferUtils.prepareBuffer(512) { + it.putProperty(PacketProperty.USER_PROPERTY, userProperties) + } + + def dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(propertiesBuffer.limit()) + it.put(propertiesBuffer) + it.putString(topicFilter) + it.putString(topicFilter2) + } + + when: + def packet = new UnsubscribeInPacket(0b1011_0000 as byte) + def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).toString() == topicFilter + packet.topicFilters.get(1).toString() == topicFilter2 + packet.packetId == packetId + packet.userProperties == userProperties + when: + + dataBuffer = BufferUtils.prepareBuffer(512) { + it.putShort(packetId) + it.putMbi(0) + it.putString(topicFilter) + it.putString(topicFilter2) + } + + packet = new UnsubscribeInPacket(0b1011_0000 as byte) + result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + then: + result + packet.topicFilters.size() == 2 + packet.topicFilters.get(0).toString() == topicFilter + packet.topicFilters.get(1).toString() == topicFilter2 + packet.packetId == packetId + packet.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Authentication5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Authentication5OutPacketTest.groovy new file mode 100644 index 00000000..2cc3fd51 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Authentication5OutPacketTest.groovy @@ -0,0 +1,39 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.AuthenticationInPacket +import javasabr.mqtt.legacy.network.packet.out.Authentication5OutPacket +import javasabr.mqtt.model.reason.code.AuthenticateReasonCode +import javasabr.rlib.common.util.BufferUtils + +class Authentication5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Authentication5OutPacket( + userProperties, + AuthenticateReasonCode.CONTINUE_AUTHENTICATION, + reasonString, + authMethod, + authData, + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new AuthenticationInPacket(0b1111_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION + reader.authenticationMethod == authMethod + reader.authenticationData == authData + reader.reason == reasonString + reader.userProperties == userProperties + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/BaseOutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/BaseOutPacketTest.groovy new file mode 100644 index 00000000..ae00469f --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/BaseOutPacketTest.groovy @@ -0,0 +1,30 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.MqttClient +import javasabr.mqtt.legacy.network.NetworkUnitSpecification +import spock.lang.Shared + +class BaseOutPacketTest extends NetworkUnitSpecification { + + @Shared + MqttClient mqtt5Client = Stub(MqttClient.UnsafeMqttClient) { + getConnectionConfig() >> mqttConnectionConfig + getSessionExpiryInterval() >> NetworkUnitSpecification.sessionExpiryInterval + getReceiveMax() >> NetworkUnitSpecification.receiveMaximum + getMaximumPacketSize() >> NetworkUnitSpecification.maximumPacketSize + getClientId() >> clientId + getKeepAlive() >> serverKeepAlive + getTopicAliasMaximum() >> NetworkUnitSpecification.topicAliasMaximum + } + + @Shared + MqttClient mqtt311Client = Stub(MqttClient.UnsafeMqttClient) { + getConnectionConfig() >> mqttConnectionConfig + getSessionExpiryInterval() >> NetworkUnitSpecification.sessionExpiryInterval + getReceiveMax() >> NetworkUnitSpecification.receiveMaximum + getMaximumPacketSize() >> NetworkUnitSpecification.maximumPacketSize + getClientId() >> clientId + getKeepAlive() >> serverKeepAlive + getTopicAliasMaximum() >> NetworkUnitSpecification.topicAliasMaximum + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect311OutPacketTest.groovy new file mode 100644 index 00000000..c0d7c8cf --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect311OutPacketTest.groovy @@ -0,0 +1,47 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.ConnectInPacket +import javasabr.mqtt.legacy.network.packet.out.Connect311OutPacket +import javasabr.mqtt.model.QoS +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class Connect311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Connect311OutPacket( + userName, + "", + clientId, + userPassword, + ArrayUtils.EMPTY_BYTE_ARRAY, + QoS.AT_MOST_ONCE, + keepAlive, + willRetain, + cleanStart, + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new ConnectInPacket(0b0001_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.username == userName + reader.clientId == clientId + reader.password == userPassword + reader.keepAlive == keepAlive + reader.userProperties == Array.empty() + reader.cleanStart == cleanStart + reader.willRetain == willRetain + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect5OutPacketTest.groovy new file mode 100644 index 00000000..f08f1691 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Connect5OutPacketTest.groovy @@ -0,0 +1,63 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.ConnectInPacket +import javasabr.mqtt.legacy.network.packet.out.Connect5OutPacket +import javasabr.mqtt.model.QoS +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class Connect5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Connect5OutPacket( + userName, + "", + clientId, + userPassword, + ArrayUtils.EMPTY_BYTE_ARRAY, + QoS.AT_MOST_ONCE, + keepAlive, + willRetain, + cleanStart, + userProperties, + authMethod, + authData, + sessionExpiryInterval, + receiveMaximum, + maximumPacketSize, + topicAliasMaximum, + requestResponseInformation, + requestProblemInformation + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new ConnectInPacket(0b0001_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.username == userName + reader.clientId == clientId + reader.password == userPassword + reader.keepAlive == keepAlive + reader.userProperties == userProperties + reader.cleanStart == cleanStart + reader.willRetain == willRetain + reader.authenticationMethod == authMethod + reader.authenticationData == authData + reader.sessionExpiryInterval == sessionExpiryInterval + reader.receiveMax == receiveMaximum + reader.maximumPacketSize == maximumPacketSize + reader.topicAliasMaximum == topicAliasMaximum + reader.requestResponseInformation == requestResponseInformation + reader.requestProblemInformation == requestProblemInformation + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck311OutPacketTest.groovy new file mode 100644 index 00000000..5fabe2b4 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck311OutPacketTest.groovy @@ -0,0 +1,52 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.legacy.network.packet.out.ConnectAck311OutPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.ArrayUtils +import javasabr.rlib.common.util.BufferUtils + +class ConnectAck311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new ConnectAck311OutPacket( + ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, + sessionPresent + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new ConnectAckInPacket(0b0010_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD + reader.sessionPresent == sessionPresent + reader.assignedClientId == "" + reader.reason == "" + reader.userProperties == Array.empty() + reader.retainAvailable == MqttProperties.RETAIN_AVAILABLE_DEFAULT + reader.wildcardSubscriptionAvailable == MqttProperties.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT + reader.subscriptionIdAvailable == MqttProperties.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT + reader.sharedSubscriptionAvailable == MqttProperties.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT + reader.responseInformation == "" + reader.serverReference == "" + reader.authenticationData == ArrayUtils.EMPTY_BYTE_ARRAY + reader.authenticationMethod == "" + reader.topicAliasMaximum == MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED + reader.serverKeepAlive == MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED + reader.receiveMax == MqttProperties.RECEIVE_MAXIMUM_UNDEFINED + reader.sessionExpiryInterval == MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED + reader.maximumPacketSize == MqttProperties.MAXIMUM_PACKET_SIZE_UNDEFINED + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck5OutPacketTest.groovy new file mode 100644 index 00000000..dc24cec1 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/ConnectAck5OutPacketTest.groovy @@ -0,0 +1,71 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.ConnectAckInPacket +import javasabr.mqtt.legacy.network.packet.out.ConnectAck5OutPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.reason.code.ConnectAckReasonCode +import javasabr.rlib.common.util.BufferUtils + +class ConnectAck5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new ConnectAck5OutPacket( + ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, + sessionPresent, + "-1", + MqttProperties.SESSION_EXPIRY_INTERVAL_UNDEFINED, + MqttProperties.SERVER_KEEP_ALIVE_UNDEFINED, + MqttProperties.TOPIC_ALIAS_MAXIMUM_UNDEFINED, + reasonString, + serverReference, + responseInformation, + authMethod, + authData, + userProperties, + clientId, + maxQos, + sessionExpiryInterval, + maximumPacketSize, + receiveMaximum, + topicAliasMaximum, + serverKeepAlive, + retainAvailable, + wildcardSubscriptionAvailable, + subscriptionIdAvailable, + sharedSubscriptionAvailable + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new ConnectAckInPacket(0b0010_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD + reader.sessionPresent == sessionPresent + reader.retainAvailable == retainAvailable + reader.sessionExpiryInterval == sessionExpiryInterval + reader.receiveMax == receiveMaximum + reader.maximumPacketSize == maximumPacketSize + reader.assignedClientId == clientId + reader.topicAliasMaximum == topicAliasMaximum + reader.reason == reasonString + reader.userProperties == userProperties + reader.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable + reader.subscriptionIdAvailable == subscriptionIdAvailable + reader.sharedSubscriptionAvailable == sharedSubscriptionAvailable + reader.serverKeepAlive == serverKeepAlive + reader.responseInformation == responseInformation + reader.serverReference == serverReference + reader.authenticationData == authData + reader.authenticationMethod == authMethod + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/DisconnectAck5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/DisconnectAck5OutPacketTest.groovy new file mode 100644 index 00000000..6bb76e0c --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/DisconnectAck5OutPacketTest.groovy @@ -0,0 +1,39 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.DisconnectInPacket +import javasabr.mqtt.legacy.network.packet.out.Disconnect5OutPacket +import javasabr.mqtt.model.reason.code.DisconnectReasonCode +import javasabr.rlib.common.util.BufferUtils + +class DisconnectAck5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Disconnect5OutPacket( + DisconnectReasonCode.PACKET_TOO_LARGE, + userProperties, + reasonString, + serverReference, + sessionExpiryInterval + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new DisconnectInPacket(0b1110_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE + reader.userProperties == userProperties + reader.reason == reasonString + reader.serverReference == serverReference + reader.sessionExpiryInterval == sessionExpiryInterval + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish311OutPacketTest.groovy new file mode 100644 index 00000000..9e2ccb5d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish311OutPacketTest.groovy @@ -0,0 +1,68 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket +import javasabr.mqtt.legacy.network.packet.out.Publish311OutPacket +import javasabr.mqtt.model.QoS +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class Publish311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new Publish311OutPacket( + packetId, + QoS.EXACTLY_ONCE, + true, + true, + publishTopic.toString(), + publishPayload + ) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishInPacket(0b0011_1101 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == packetId + reader.qos == QoS.EXACTLY_ONCE + reader.retained + reader.duplicate + reader.payload == publishPayload + reader.topicName == publishTopic + reader.userProperties == Array.empty() + when: + + packet = new Publish311OutPacket( + packetId, + QoS.AT_MOST_ONCE, + false, + false, + publishTopic.toString(), + publishPayload + ) + + dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + reader = new PublishInPacket(0b0011_0000 as byte) + result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == 0 + reader.qos == QoS.AT_MOST_ONCE + !reader.retained + !reader.duplicate + reader.payload == publishPayload + reader.topicName == publishTopic + reader.userProperties == Array.empty() + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish5OutPacketTest.groovy new file mode 100644 index 00000000..9af90fc9 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Publish5OutPacketTest.groovy @@ -0,0 +1,85 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishInPacket +import javasabr.mqtt.legacy.network.packet.out.Publish5OutPacket +import javasabr.mqtt.model.QoS +import javasabr.rlib.common.util.BufferUtils + +class Publish5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new Publish5OutPacket( + packetId, + QoS.EXACTLY_ONCE, + true, + true, + publishTopic.toString(), + publishPayload, + topicAlias, + false, + responseTopic, + correlationData, + userProperties + ) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishInPacket(0b0011_1101 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == packetId + reader.qos == QoS.EXACTLY_ONCE + reader.retained + reader.duplicate + reader.payload == publishPayload + reader.topicName == publishTopic + reader.userProperties == userProperties + reader.topicAlias == topicAlias + !reader.payloadFormatIndicator + reader.responseTopic == responseTopic + reader.correlationData == correlationData + when: + + packet = new Publish5OutPacket( + packetId, + QoS.AT_MOST_ONCE, + false, + false, + publishTopic.toString(), + publishPayload, + topicAlias, + true, + responseTopic, + correlationData, + userProperties + ) + + dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + reader = new PublishInPacket(0b0011_0000 as byte) + result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == 0 + reader.qos == QoS.AT_MOST_ONCE + !reader.retained + !reader.duplicate + reader.payload == publishPayload + reader.topicName == publishTopic + reader.userProperties == userProperties + reader.topicAlias == topicAlias + reader.payloadFormatIndicator + reader.responseTopic == responseTopic + reader.correlationData == correlationData + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck311OutPacketTest.groovy new file mode 100644 index 00000000..afed9e48 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck311OutPacketTest.groovy @@ -0,0 +1,31 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishAck311OutPacket +import javasabr.mqtt.model.reason.code.PublishAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishAck311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new PublishAck311OutPacket(packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishAckInPacket(0b0100_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishAckReasonCode.SUCCESS + reader.packetId == packetId + reader.userProperties == Array.empty() + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck5OutPacketTest.groovy new file mode 100644 index 00000000..0be8abc2 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishAck5OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishAckInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishAck5OutPacket +import javasabr.mqtt.model.reason.code.PublishAckReasonCode +import javasabr.rlib.common.util.BufferUtils + +class PublishAck5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new PublishAck5OutPacket( + packetId, + PublishAckReasonCode.NOT_AUTHORIZED, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishAckInPacket(0b0100_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishAckReasonCode.NOT_AUTHORIZED + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete311OutPacketTest.groovy new file mode 100644 index 00000000..01e51be1 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete311OutPacketTest.groovy @@ -0,0 +1,31 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishComplete311OutPacket +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishComplete311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new PublishComplete311OutPacket(packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishCompleteInPacket(0b0111_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishCompletedReasonCode.SUCCESS + reader.packetId == packetId + reader.userProperties == Array.empty() + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete5OutPacketTest.groovy new file mode 100644 index 00000000..b1093bba --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishComplete5OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishCompleteInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishComplete5OutPacket +import javasabr.mqtt.model.reason.code.PublishCompletedReasonCode +import javasabr.rlib.common.util.BufferUtils + +class PublishComplete5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new PublishComplete5OutPacket( + packetId, + PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishCompleteInPacket(0b0111_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived311OutPacketTest.groovy new file mode 100644 index 00000000..32ba5edb --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived311OutPacketTest.groovy @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishReceived311OutPacket +import javasabr.mqtt.model.data.type.StringPair +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishReceived311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new PublishReceived311OutPacket(packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishReceivedInPacket(0b0101_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishReceivedReasonCode.SUCCESS + reader.packetId == packetId + reader.userProperties == Array.empty(StringPair) + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived5OutPacketTest.groovy new file mode 100644 index 00000000..bd0f6107 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishReceived5OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishReceivedInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishReceived5OutPacket +import javasabr.mqtt.model.reason.code.PublishReceivedReasonCode +import javasabr.rlib.common.util.BufferUtils + +class PublishReceived5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new PublishReceived5OutPacket( + packetId, + PublishReceivedReasonCode.UNSPECIFIED_ERROR, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishReceivedInPacket(0b0101_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishReceivedReasonCode.UNSPECIFIED_ERROR + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease311OutPacketTest.groovy new file mode 100644 index 00000000..0139af29 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease311OutPacketTest.groovy @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishRelease311OutPacket +import javasabr.mqtt.model.data.type.StringPair +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class PublishRelease311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new PublishRelease311OutPacket(packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishReleaseInPacket(0b0110_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishReleaseReasonCode.SUCCESS + reader.packetId == packetId + reader.userProperties == Array.empty(StringPair) + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease5OutPacketTest.groovy new file mode 100644 index 00000000..2c9e2b95 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/PublishRelease5OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.PublishReleaseInPacket +import javasabr.mqtt.legacy.network.packet.out.PublishRelease5OutPacket +import javasabr.mqtt.model.reason.code.PublishReleaseReasonCode +import javasabr.rlib.common.util.BufferUtils + +class PublishRelease5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new PublishRelease5OutPacket( + packetId, + PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new PublishReleaseInPacket(0b0110_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCode == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe311OutPacketTest.groovy new file mode 100644 index 00000000..445590c0 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe311OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.SubscribeInPacket +import javasabr.mqtt.legacy.network.packet.out.Subscribe311OutPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.mqtt.model.data.type.StringPair +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class Subscribe311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Subscribe311OutPacket( + topicFiltersObj311, + 1 + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new SubscribeInPacket(0b1000_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == 1 + reader.topicFilters == topicFiltersObj311 + reader.userProperties == Array.empty(StringPair) + reader.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe5OutPacketTest.groovy new file mode 100644 index 00000000..08eff37d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/Subscribe5OutPacketTest.groovy @@ -0,0 +1,37 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.SubscribeInPacket +import javasabr.mqtt.legacy.network.packet.out.Subscribe5OutPacket +import javasabr.mqtt.model.MqttProperties +import javasabr.rlib.common.util.BufferUtils + +class Subscribe5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new Subscribe5OutPacket( + topicFiltersObj5, + 1, + userProperties, + MqttProperties.SUBSCRIPTION_ID_UNDEFINED + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new SubscribeInPacket(0b1000_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.packetId == 1 + reader.topicFilters == topicFiltersObj5 + reader.userProperties == userProperties + reader.subscriptionId == MqttProperties.SUBSCRIPTION_ID_UNDEFINED + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck311OutPacketTest.groovy new file mode 100644 index 00000000..cfb681df --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck311OutPacketTest.groovy @@ -0,0 +1,31 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.SubscribeAck311OutPacket +import javasabr.mqtt.model.data.type.StringPair +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class SubscribeAck311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new SubscribeAck311OutPacket(subscribeAckReasonCodes, packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new SubscribeAckInPacket(0b1001_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCodes == subscribeAckReasonCodes + reader.packetId == packetId + reader.userProperties == Array.empty(StringPair) + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck5OutPacketTest.groovy new file mode 100644 index 00000000..125dff15 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/SubscribeAck5OutPacketTest.groovy @@ -0,0 +1,36 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.SubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.SubscribeAck5OutPacket +import javasabr.rlib.common.util.BufferUtils + +class SubscribeAck5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new SubscribeAck5OutPacket( + packetId, + subscribeAckReasonCodes, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new SubscribeAckInPacket(0b1001_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCodes == subscribeAckReasonCodes + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck311OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck311OutPacketTest.groovy new file mode 100644 index 00000000..9532847d --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck311OutPacketTest.groovy @@ -0,0 +1,32 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.UnsubscribeAck311OutPacket +import javasabr.mqtt.model.data.type.StringPair +import javasabr.mqtt.model.reason.code.UnsubscribeAckReasonCode +import javasabr.rlib.collections.array.Array +import javasabr.rlib.common.util.BufferUtils + +class UnsubscribeAck311OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + def packet = new UnsubscribeAck311OutPacket(packetId) + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new UnsubscribeAckInPacket(0b1011_0000 as byte) + def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCodes == Array.empty(UnsubscribeAckReasonCode) + reader.packetId == packetId + reader.userProperties == Array.empty(StringPair) + reader.reason == "" + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck5OutPacketTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck5OutPacketTest.groovy new file mode 100644 index 00000000..e342949b --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/network/out/UnsubscribeAck5OutPacketTest.groovy @@ -0,0 +1,36 @@ +package javasabr.mqtt.legacy.network.out + +import javasabr.mqtt.legacy.network.packet.in.UnsubscribeAckInPacket +import javasabr.mqtt.legacy.network.packet.out.UnsubscribeAck5OutPacket +import javasabr.rlib.common.util.BufferUtils + +class UnsubscribeAck5OutPacketTest extends BaseOutPacketTest { + + def "should write packet correctly"() { + + given: + + def packet = new UnsubscribeAck5OutPacket( + packetId, + unsubscribeAckReasonCodes, + userProperties, + reasonString + ) + + when: + + def dataBuffer = BufferUtils.prepareBuffer(512) { + packet.write(it) + } + + def reader = new UnsubscribeAckInPacket(0b1011_0000 as byte) + def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) + + then: + result + reader.reasonCodes == unsubscribeAckReasonCodes + reader.packetId == packetId + reader.userProperties == userProperties + reader.reason == reasonString + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/util/MqttDataUtilsTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/util/MqttDataUtilsTest.groovy new file mode 100644 index 00000000..cfa63718 --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/util/MqttDataUtilsTest.groovy @@ -0,0 +1,61 @@ +package javasabr.mqtt.legacy.util + +import javasabr.mqtt.legacy.UnitSpecification + +import java.nio.ByteBuffer + +class MqttDataUtilsTest extends UnitSpecification { + + def "should write integer to MQTT multi byte integer successful"(int value, int expectedBytes) { + given: + def buffer = ByteBuffer.allocate(4) + when: + MqttDataUtils.writeMbi(value, buffer) + then: + buffer.position() == expectedBytes + where: + value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] + expectedBytes << [1, 2, 3, 3, 3, 4] + } + + def "should failed writing too big integer to MQTT multi byte integer"(int value) { + given: + def buffer = ByteBuffer.allocate(10) + when: + MqttDataUtils.writeMbi(value, buffer) + then: + thrown IllegalArgumentException + where: + value << [1_000_000_000, 2_000_000_000, 5_000_000_000] + } + + def "should read integer from MQTT multi byte integer successful"(int value) { + given: + def buffer = ByteBuffer.allocate(5) + MqttDataUtils.writeMbi(value, buffer).flip() + when: + def read = MqttDataUtils.readMbi(buffer) + then: + read == value + where: + value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] + } + + def "should failed reading integer from MQTT multi byte integer"(int value, int position) { + given: + + def buffer = ByteBuffer.allocate(10) + + MqttDataUtils.writeMbi(value, buffer) + .position(position) + .flip() + + when: + def read = MqttDataUtils.readMbi(buffer) + then: + read == -1 + where: + value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] + position << [0, 0, 1, 1, 2, 2] + } +} diff --git a/legacy/src/test/groovy/javasabr/mqtt/legacy/util/TopicUtilsTest.groovy b/legacy/src/test/groovy/javasabr/mqtt/legacy/util/TopicUtilsTest.groovy new file mode 100644 index 00000000..fda697bc --- /dev/null +++ b/legacy/src/test/groovy/javasabr/mqtt/legacy/util/TopicUtilsTest.groovy @@ -0,0 +1,58 @@ +package javasabr.mqtt.legacy.util + +import spock.lang.Specification +import spock.lang.Unroll + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicName +import static javasabr.mqtt.model.utils.TopicUtils.isInvalid + +class TopicUtilsTest extends Specification { + + @Unroll + def "should create valid topic name: [#topicName]"() { + expect: + !isInvalid(buildTopicName(topicName)) + where: + topicName | _ + "topic/Name" | _ + "topic" | _ + } + + @Unroll + def "should create valid topic filter: [#topicFilter]"() { + expect: + !isInvalid(buildTopicFilter(topicFilter)) + where: + topicFilter | _ + "topic/Filter" | _ + "topic/+" | _ + "topic/+/Filter" | _ + "topic/#" | _ + } + + @Unroll + def "should detect invalid topic name: [#topicName]"() { + expect: + isInvalid(buildTopicName(topicName)) + where: + topicName | _ + "topic/+" | _ + "topic/" | _ + "topic//Name" | _ + "topic/#" | _ + } + + @Unroll + def "should detect invalid topic filter: [#topicFilter]"() { + expect: + isInvalid(buildTopicFilter(topicFilter)) + where: + topicFilter | _ + "topic/" | _ + "/topic" | _ + "topic//Name" | _ + "topic/##" | _ + "#/Filter" | _ + } +} diff --git a/legacy/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule b/legacy/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule new file mode 100644 index 00000000..7fb642a1 --- /dev/null +++ b/legacy/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule @@ -0,0 +1,3 @@ +moduleName=test-specification +moduleVersion=1.0 +extensionClasses=javasabr.mqtt.legacy.extension.SpecificationExtensions diff --git a/src/test/resources/application-test.properties b/legacy/src/test/resources/application-test.properties similarity index 100% rename from src/test/resources/application-test.properties rename to legacy/src/test/resources/application-test.properties diff --git a/legacy/src/test/resources/credentials-test b/legacy/src/test/resources/credentials-test new file mode 100644 index 00000000..9f363386 --- /dev/null +++ b/legacy/src/test/resources/credentials-test @@ -0,0 +1,2 @@ +user=password +user1=password diff --git a/src/test/resources/disabled-features.properties b/legacy/src/test/resources/disabled-features.properties similarity index 100% rename from src/test/resources/disabled-features.properties rename to legacy/src/test/resources/disabled-features.properties diff --git a/src/test/resources/log4j2.xml b/legacy/src/test/resources/log4j2.xml similarity index 70% rename from src/test/resources/log4j2.xml rename to legacy/src/test/resources/log4j2.xml index 5332d8fa..a1e31db8 100644 --- a/src/test/resources/log4j2.xml +++ b/legacy/src/test/resources/log4j2.xml @@ -6,10 +6,10 @@ - + - + diff --git a/model/build.gradle b/model/build.gradle new file mode 100644 index 00000000..a46c7f27 --- /dev/null +++ b/model/build.gradle @@ -0,0 +1,8 @@ +apply plugin: "java-library" +apply from: "$rootDir/gradle/configure-java.gradle" + +dependencies { + api projects.base + + testImplementation projects.testSupport +} \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/ActionResult.java b/model/src/main/java/javasabr/mqtt/model/ActionResult.java new file mode 100644 index 00000000..fd7495a6 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/ActionResult.java @@ -0,0 +1,17 @@ +package javasabr.mqtt.model; + +public enum ActionResult { + SUCCESS, + FAILED, + EMPTY; + + public ActionResult and(ActionResult another) { + if (this == FAILED || another == FAILED) { + return FAILED; + } else if (this == SUCCESS || another == SUCCESS) { + return SUCCESS; + } else { + return EMPTY; + } + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttConnectionConfig.java b/model/src/main/java/javasabr/mqtt/model/MqttConnectionConfig.java new file mode 100644 index 00000000..adff53d8 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/MqttConnectionConfig.java @@ -0,0 +1,25 @@ +package javasabr.mqtt.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class MqttConnectionConfig { + + private final QoS maxQos; + + private final int maximumPacketSize; + private final int minKeepAliveTime; + private final int receiveMaximum; + private final int topicAliasMaximum; + + private final long defaultSessionExpiryInterval; + + private final boolean keepAliveEnabled; + private final boolean sessionsEnabled; + private final boolean retainAvailable; + private final boolean wildcardSubscriptionAvailable; + private final boolean subscriptionIdAvailable; + private final boolean sharedSubscriptionAvailable; +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttProperties.java b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java new file mode 100644 index 00000000..f4b859ef --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/MqttProperties.java @@ -0,0 +1,55 @@ +package javasabr.mqtt.model; + +public interface MqttProperties { + + QoS MAXIMUM_QOS_DEFAULT = QoS.EXACTLY_ONCE; + + int MAXIMUM_PROTOCOL_PACKET_SIZE = 256 * 1024 * 1024; + int MAXIMUM_PACKET_ID = 0xFFFF; + + long SESSION_EXPIRY_INTERVAL_DISABLED = 0; + long SESSION_EXPIRY_INTERVAL_DEFAULT = 120; + long SESSION_EXPIRY_INTERVAL_MIN = 0; + long SESSION_EXPIRY_INTERVAL_INFINITY = 0xFFFFFFFFL; + long SESSION_EXPIRY_INTERVAL_UNDEFINED = -1; + + int RECEIVE_MAXIMUM_UNDEFINED = -1; + int RECEIVE_MAXIMUM_MIN = 1; + int RECEIVE_MAXIMUM_DEFAULT = 10; + int RECEIVE_MAXIMUM_MAX = 0xFFFF; + + int MAXIMUM_PACKET_SIZE_UNDEFINED = -1; + int MAXIMUM_PACKET_SIZE_DEFAULT = 1024; + int MAXIMUM_PACKET_SIZE_MIN = 1; + int MAXIMUM_PACKET_SIZE_MAX = MAXIMUM_PROTOCOL_PACKET_SIZE; + + boolean PAYLOAD_FORMAT_INDICATOR_DEFAULT = false; + + long MESSAGE_EXPIRY_INTERVAL_UNDEFINED = -1; + long MESSAGE_EXPIRY_INTERVAL_INFINITY = 0; + + int TOPIC_ALIAS_MAXIMUM_UNDEFINED = -1; + int TOPIC_ALIAS_MAXIMUM_DISABLED = 0; + + int SERVER_KEEP_ALIVE_UNDEFINED = -1; + int SERVER_KEEP_ALIVE_DISABLED = 0; + int SERVER_KEEP_ALIVE_DEFAULT = 0; + int SERVER_KEEP_ALIVE_MIN = 0; + int SERVER_KEEP_ALIVE_MAX = 0xFFFF; + + int TOPIC_ALIAS_DEFAULT = 0; + int TOPIC_ALIAS_MIN = 0; + int TOPIC_ALIAS_MAX = 0xFFFF; + int TOPIC_ALIAS_NOT_SET = 0; + + int SUBSCRIPTION_ID_UNDEFINED = 0; + + boolean SESSIONS_ENABLED_DEFAULT = true; + boolean KEEP_ALIVE_ENABLED_DEFAULT = false; + boolean RETAIN_AVAILABLE_DEFAULT = false; + boolean WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT = false; + boolean SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT = false; + boolean SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT = false; + + int PACKET_ID_FOR_QOS_0 = 0; +} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttUser.java b/model/src/main/java/javasabr/mqtt/model/MqttUser.java new file mode 100644 index 00000000..f7f686ed --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/MqttUser.java @@ -0,0 +1,3 @@ +package javasabr.mqtt.model; + +public interface MqttUser {} diff --git a/model/src/main/java/javasabr/mqtt/model/MqttVersion.java b/model/src/main/java/javasabr/mqtt/model/MqttVersion.java new file mode 100644 index 00000000..ed91e126 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/MqttVersion.java @@ -0,0 +1,69 @@ +package javasabr.mqtt.model; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import lombok.Getter; + +@Getter +public enum MqttVersion { + UNKNOWN("Unknown", -1), + MQTT_3_1_1("MQTT", 4), + MQTT_5("MQTT", 5); + + private static final Map NAME_LEVEL_VERSIONS; + + static { + + var map = new HashMap(); + + for (var mqttVersion : values()) { + + if (mqttVersion.version < 0) { + continue; + } + + var versions = map.computeIfAbsent(mqttVersion.name, name -> new MqttVersion[mqttVersion.version + 1]); + + if (versions.length > mqttVersion.version) { + versions[mqttVersion.version] = mqttVersion; + continue; + } + + versions = Arrays.copyOf(versions, mqttVersion.version + 1); + versions[mqttVersion.version] = mqttVersion; + + map.replace(mqttVersion.name, versions); + } + + NAME_LEVEL_VERSIONS = Map.copyOf(map); + } + + public static MqttVersion of(String name, byte level) { + + if (level < 0) { + return MqttVersion.UNKNOWN; + } + + var availableVersions = NAME_LEVEL_VERSIONS.get(name); + + if (availableVersions == null) { + return MqttVersion.UNKNOWN; + } else if (availableVersions.length <= level || availableVersions[level] == null) { + return MqttVersion.UNKNOWN; + } + + return availableVersions[level]; + } + + private final byte[] nameInBytes; + private final String name; + private final byte version; + + MqttVersion(String name, int version) { + this.name = name; + this.version = (byte) version; + this.nameInBytes = name.getBytes(StandardCharsets.UTF_8); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/PacketProperty.java b/model/src/main/java/javasabr/mqtt/model/PacketProperty.java new file mode 100644 index 00000000..d4ad72fe --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/PacketProperty.java @@ -0,0 +1,87 @@ +package javasabr.mqtt.model; + +import javasabr.mqtt.model.data.type.PacketDataType; +import java.util.stream.Stream; +import javasabr.rlib.common.util.ClassUtils; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import org.jspecify.annotations.Nullable; + +public enum PacketProperty { + PAYLOAD_FORMAT_INDICATOR(0x01, PacketDataType.BYTE), + MESSAGE_EXPIRY_INTERVAL(0x02, PacketDataType.INTEGER), + CONTENT_TYPE(0x03, PacketDataType.UTF_8_STRING), + RESPONSE_TOPIC(0x08, PacketDataType.UTF_8_STRING), + CORRELATION_DATA(0x09, PacketDataType.BINARY), + SUBSCRIPTION_IDENTIFIER(0x0B, PacketDataType.MULTI_BYTE_INTEGER), + SESSION_EXPIRY_INTERVAL(0x11, PacketDataType.INTEGER), + ASSIGNED_CLIENT_IDENTIFIER(0x12, PacketDataType.UTF_8_STRING), + SERVER_KEEP_ALIVE(0x13, PacketDataType.SHORT), + AUTHENTICATION_METHOD(0x15, PacketDataType.UTF_8_STRING), + AUTHENTICATION_DATA(0x16, PacketDataType.BINARY), + REQUEST_PROBLEM_INFORMATION(0x17, PacketDataType.BYTE), + WILL_DELAY_INTERVAL(0x18, PacketDataType.INTEGER), + REQUEST_RESPONSE_INFORMATION(0x19, PacketDataType.BYTE), + RESPONSE_INFORMATION(0x1A, PacketDataType.UTF_8_STRING), + SERVER_REFERENCE(0x1C, PacketDataType.UTF_8_STRING), + REASON_STRING(0x1F, PacketDataType.UTF_8_STRING), + RECEIVE_MAXIMUM(0x21, PacketDataType.SHORT), + TOPIC_ALIAS_MAXIMUM(0x22, PacketDataType.SHORT), + TOPIC_ALIAS(0x23, PacketDataType.SHORT), + MAXIMUM_QOS(0x24, PacketDataType.BYTE), + RETAIN_AVAILABLE(0x25, PacketDataType.BYTE), + USER_PROPERTY(0x26, PacketDataType.UTF_8_STRING_PAIR), + MAXIMUM_PACKET_SIZE(0x27, PacketDataType.INTEGER), + WILDCARD_SUBSCRIPTION_AVAILABLE(0x28, PacketDataType.BYTE), + SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29, PacketDataType.BYTE), + SHARED_SUBSCRIPTION_AVAILABLE(0x2A, PacketDataType.BYTE); + + private static final PacketProperty[] PROPERTIES; + + static { + + int maxId = Stream + .of(values()) + .mapToInt(PacketProperty::getId) + .max() + .orElse(0); + + var result = new PacketProperty[maxId + 1]; + + Stream + .of(values()) + .forEach(prop -> result[prop.id] = prop); + + PROPERTIES = result; + } + + public static PacketProperty of(int id) { + if (id < 0 || id >= PROPERTIES.length) { + throw new IllegalArgumentException("Unknown property with id: " + id); + } else { + return PROPERTIES[id]; + } + } + + @Getter + private final byte id; + @Getter + private final PacketDataType dataType; + + @Nullable + private final Object defaultValue; + + PacketProperty(int id, PacketDataType dataType) { + this(id, dataType, null); + } + + PacketProperty(int id, PacketDataType dataType, @Nullable Object defaultValue) { + this.id = (byte) id; + this.dataType = dataType; + this.defaultValue = defaultValue; + } + + public T getDefaultValue() { + return ClassUtils.unsafeNNCast(ObjectUtils.notNull(defaultValue)); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/QoS.java b/model/src/main/java/javasabr/mqtt/model/QoS.java new file mode 100644 index 00000000..9b0d7367 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/QoS.java @@ -0,0 +1,26 @@ +package javasabr.mqtt.model; + +import javasabr.mqtt.model.reason.code.SubscribeAckReasonCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum QoS { + AT_MOST_ONCE(SubscribeAckReasonCode.GRANTED_QOS_0), + AT_LEAST_ONCE(SubscribeAckReasonCode.GRANTED_QOS_1), + EXACTLY_ONCE(SubscribeAckReasonCode.GRANTED_QOS_2), + INVALID(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR); + + private static final QoS[] VALUES = values(); + + public static QoS of(int level) { + if (level < 0 || level > EXACTLY_ONCE.ordinal()) { + return INVALID; + } else { + return VALUES[level]; + } + } + + private final SubscribeAckReasonCode subscribeAckReasonCode; +} diff --git a/model/src/main/java/javasabr/mqtt/model/SubscribeRetainHandling.java b/model/src/main/java/javasabr/mqtt/model/SubscribeRetainHandling.java new file mode 100644 index 00000000..57f31fff --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/SubscribeRetainHandling.java @@ -0,0 +1,27 @@ +package javasabr.mqtt.model; + +public enum SubscribeRetainHandling { + /** + * Send retained messages at the time of the subscribe. + */ + SEND, + /** + * Send retained messages at subscribe only if the subscription does not currently exist. + */ + SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST, + /** + * Do not send retained messages at the time of the subscribe. + */ + DO_NOT_SEND, + INVALID; + + private static final SubscribeRetainHandling[] VALUES = values(); + + public static SubscribeRetainHandling of(int level) { + if (level < 0 || level > DO_NOT_SEND.ordinal()) { + return SubscribeRetainHandling.INVALID; + } else { + return VALUES[level]; + } + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java b/model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java new file mode 100644 index 00000000..488924bf --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/data/type/PacketDataType.java @@ -0,0 +1,11 @@ +package javasabr.mqtt.model.data.type; + +public enum PacketDataType { + BYTE, + SHORT, + INTEGER, + MULTI_BYTE_INTEGER, + BINARY, + UTF_8_STRING, + UTF_8_STRING_PAIR +} diff --git a/src/main/java/com/ss/mqtt/broker/model/data/type/StringPair.java b/model/src/main/java/javasabr/mqtt/model/data/type/StringPair.java similarity index 58% rename from src/main/java/com/ss/mqtt/broker/model/data/type/StringPair.java rename to model/src/main/java/javasabr/mqtt/model/data/type/StringPair.java index 943c4c1f..4cb4483d 100644 --- a/src/main/java/com/ss/mqtt/broker/model/data/type/StringPair.java +++ b/model/src/main/java/javasabr/mqtt/model/data/type/StringPair.java @@ -1,10 +1,9 @@ -package com.ss.mqtt.broker.model.data.type; +package javasabr.mqtt.model.data.type; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import org.jetbrains.annotations.NotNull; @Getter @ToString @@ -12,6 +11,6 @@ @RequiredArgsConstructor public class StringPair { - private final String name; - private final String value; + private final String name; + private final String value; } diff --git a/src/main/java/com/ss/mqtt/broker/model/impl/package-info.java b/model/src/main/java/javasabr/mqtt/model/data/type/package-info.java similarity index 58% rename from src/main/java/com/ss/mqtt/broker/model/impl/package-info.java rename to model/src/main/java/javasabr/mqtt/model/data/type/package-info.java index b2600a84..5045f616 100644 --- a/src/main/java/com/ss/mqtt/broker/model/impl/package-info.java +++ b/model/src/main/java/javasabr/mqtt/model/data/type/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.model.impl; +package javasabr.mqtt.model.data.type; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/util/package-info.java b/model/src/main/java/javasabr/mqtt/model/package-info.java similarity index 62% rename from src/main/java/com/ss/mqtt/broker/util/package-info.java rename to model/src/main/java/javasabr/mqtt/model/package-info.java index 54bcc6ee..aad6c143 100644 --- a/src/main/java/com/ss/mqtt/broker/util/package-info.java +++ b/model/src/main/java/javasabr/mqtt/model/package-info.java @@ -1,4 +1,4 @@ @NullMarked -package com.ss.mqtt.broker.util; +package javasabr.mqtt.model; import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/AuthenticateReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/AuthenticateReasonCode.java new file mode 100644 index 00000000..4d2efe4b --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/AuthenticateReasonCode.java @@ -0,0 +1,52 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum AuthenticateReasonCode { + + /** + * Authentication is successful. Server. + */ + SUCCESS((byte) 0x00), + /** + * Continue the authentication with another step. Client or Server. + */ + CONTINUE_AUTHENTICATION((byte) 0x18), + /** + * Initiate a re-authentication. Client. + */ + RE_AUTHENTICATE((byte) 0x19); + + private static final AuthenticateReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(AuthenticateReasonCode::getValue) + .max() + .orElse(0); + + var values = new AuthenticateReasonCode[maxId + 1]; + + for (var value : values()) { + values[value.value] = value; + } + + VALUES = values; + } + + public static AuthenticateReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + private @Getter + final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/ConnectAckReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/ConnectAckReasonCode.java new file mode 100644 index 00000000..99f6d075 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/ConnectAckReasonCode.java @@ -0,0 +1,156 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum ConnectAckReasonCode { + /** + * The Connection is accepted. + */ + SUCCESS((byte) 0x00, (byte) 0x00), + + // WITH REASONS BELOW SERVER MUST CLOSE CONNECTION + + /** + * The Server does not wish to reveal the reason for the failure, or none of the other Reason Codes apply. + */ + UNSPECIFIED_ERROR((byte) 0x01, (byte) 0x80), + /** + * Data within the CONNECT packet could not be correctly parsed. + */ + MALFORMED_PACKET((byte) 0x01, (byte) 0x81), + /** + * Data in the CONNECT packet does not conform to this specification. + */ + PROTOCOL_ERROR((byte) 0x01, (byte) 0x82), + /** + * The CONNECT is valid but is not accepted by this Server. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x01, (byte) 0x83), + /** + * The Server does not support the version of the MQTT protocol requested by the Client + */ + UNSUPPORTED_PROTOCOL_VERSION((byte) 0x01, (byte) 0x84), + /** + * The Client Identifier is a valid string but is not allowed by the Server. + */ + CLIENT_IDENTIFIER_NOT_VALID((byte) 0x02, (byte) 0x85), + /** + * The Server does not accept the User Name or Password specified by the Client + */ + BAD_USER_NAME_OR_PASSWORD((byte) 0x04, (byte) 0x86), + /** + * The Client is not authorized to connect. + */ + NOT_AUTHORIZED((byte) 0x05, (byte) 0x87), + /** + * The MQTT Server is not available + */ + SERVER_UNAVAILABLE((byte) 0x03, (byte) 0x88), + /** + * The Server is busy. Try again later. + */ + SERVER_BUSY((byte) 0x01, (byte) 0x89), + /** + * This Client has been banned by administrative action. Contact the server administrator. + */ + BANNED((byte) 0x01, (byte) 0x8A), + /** + * The authentication method is not supported or does not match the authentication method currently in use. + */ + BAD_AUTHENTICATION_METHOD((byte) 0x04, (byte) 0x8C), + /** + * The Will Topic Name is not malformed, but is not accepted by this Server. + */ + TOPIC_NAME_INVALID((byte) 0x01, (byte) 0x90), + /** + * The CONNECT packet exceeded the maximum permissible size. + */ + PACKET_TOO_LARGE((byte) 0x01, (byte) 0x95), + /** + * An implementation or administrative imposed limit has been exceeded. + */ + QUOTA_EXCEEDED((byte) 0x01, (byte) 0x97), + /** + * The Will Payload does not match the specified Payload Format Indicator. + */ + PAYLOAD_FORMAT_INVALID((byte) 0x01, (byte) 0x99), + /** + * The Server does not support retained messages, and Will Retain was set to 1. + */ + RETAIN_NOT_SUPPORTED((byte) 0x01, (byte) 0x9A), + /** + * The Server does not support the QoS set in Will QoS. + */ + QOS_NOT_SUPPORTED((byte) 0x01, (byte) 0x9B), + /** + * The Client should temporarily use another server. + */ + USE_ANOTHER_SERVER((byte) 0x01, (byte) 0x9C), + /** + * The Client should permanently use another server. + */ + SERVER_MOVED((byte) 0x01, (byte) 0x9D), + /** + * The connection rate limit has been exceeded. + */ + CONNECTION_RATE_EXCEEDED((byte) 0x01, (byte) 0x9F); + + private static final ConnectAckReasonCode[] MQTT_5_VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(ConnectAckReasonCode::getMqtt5) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new ConnectAckReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.mqtt5)] = value; + } + + MQTT_5_VALUES = values; + } + + public static ConnectAckReasonCode of(boolean mqtt5, int reasonCode) { + return mqtt5 ? ofMqtt5(reasonCode) : ofMqtt311(reasonCode); + } + + public static ConnectAckReasonCode ofMqtt311(int reasonCode) { + switch (reasonCode) { + case 0x00: + return SUCCESS; + case 0x01: + return UNSUPPORTED_PROTOCOL_VERSION; + case 0x02: + return CLIENT_IDENTIFIER_NOT_VALID; + case 0x03: + return SERVER_UNAVAILABLE; + case 0x04: + return BAD_USER_NAME_OR_PASSWORD; + case 0x05: + return NOT_AUTHORIZED; + default: + throw new IllegalArgumentException("Unsupported reason code: " + reasonCode); + } + } + + public static ConnectAckReasonCode ofMqtt5(int reasonCode) { + return ObjectUtils.notNull( + MQTT_5_VALUES[reasonCode], + reasonCode, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte mqtt311; + @Getter + private final byte mqtt5; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java new file mode 100644 index 00000000..5849c17c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/DisconnectReasonCode.java @@ -0,0 +1,162 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum DisconnectReasonCode { + /** + * Close the connection normally. Do not send the Will Message. Client or Server. + */ + NORMAL_DISCONNECTION((byte) 0x00), + /** + * The Client wishes to disconnect but requires that the Server also publishes its Will Message. Client. + */ + DISCONNECT_WITH_WILL_MESSAGE((byte) 0x04), + + // ERRORS + + /** + * The Connection is closed but the sender either does not wish to reveal the reason, or none of the other Reason + * Codes apply. Client or Server. + */ + UNSPECIFIED_ERROR((byte) 0x80), + /** + * The received packet does not conform to this specification. Client or Server. + */ + MALFORMED_PACKET((byte) 0x81), + /** + * An unexpected or out of order packet was received. Client or Server. + */ + PROTOCOL_ERROR((byte) 0x82), + /** + * The packet received is valid but cannot be processed by this implementation. Client or Server. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + /** + * The request is not authorized. Server. + */ + NOT_AUTHORIZED((byte) 0x87), + /** + * The Server is busy and cannot continue processing requests from this Client. Server. + */ + SERVER_BUSY((byte) 0x89), + /** + * The Server is shutting down. Server. + */ + SERVER_SHUTTING_DOWN((byte) 0x8B), + /** + * The Connection is closed because no packet has been received for 1.5 times the Keepalive time. Server. + */ + KEEP_ALIVE_TIMEOUT((byte) 0x8D), + /** + * Another Connection using the same ClientID has connected causing this Connection to be closed. Server. + */ + SESSION_TAKEN_OVER((byte) 0x8E), + /** + * The Topic Filter is correctly formed, but is not accepted by this Sever. Server. + */ + TOPIC_FILTER_INVALID((byte) 0x8F), + /** + * The Topic Name is correctly formed, but is not accepted by this Client or Server. Client or Server. + */ + TOPIC_NAME_INVALID((byte) 0x90), + /** + * The Client or Server has received more than Receive Maximum publication for which it has not sent PUBACK or + * PUBCOMP. Client or Server. + */ + RECEIVE_MAXIMUM_EXCEEDED((byte) 0x93), + /** + * The Client or Server has received a PUBLISH packet containing a Topic Alias which is greater than the Maximum Topic + * Alias it sent in the CONNECT or CONNACK packet. Client or Server. + */ + TOPIC_ALIAS_INVALID((byte) 0x94), + /** + * The packet size is greater than Maximum Packet Size for this Client or Server. Client or Server. + */ + PACKET_TOO_LARGE((byte) 0x95), + /** + * The received data rate is too high. Client or Server. + */ + MESSAGE_RATE_TOO_HIGH((byte) 0x96), + /** + * An implementation or administrative imposed limit has been exceeded. Client or Server. + */ + QUOTA_EXCEEDED((byte) 0x97), + /** + * The Connection is closed due to an administrative action. Client or Server. + */ + ADMINISTRATIVE_ACTION((byte) 0x98), + /** + * The payload format does not match the one specified by the Payload Format Indicator. Client or Server. + */ + PAYLOAD_FORMAT_INVALID((byte) 0x99), + /** + * The Server has does not support retained messages. Server. + */ + RETAIN_NOT_SUPPORTED((byte) 0x9A), + /** + * The Client specified a QoS greater than the QoS specified in a Maximum QoS in the CONNACK. Server. + */ + QOS_NOT_SUPPORTED((byte) 0x9B), + /** + * The Client should temporarily change its Server. Server. + */ + USE_ANOTHER_SERVER((byte) 0x9C), + /** + * The Server is moved and the Client should permanently change its server location. Server. + */ + SERVER_MOVED((byte) 0x9D), + /** + * The Server does not support Shared Subscriptions. Server. + */ + SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), + /** + * This connection is closed because the connection rate is too high. Server. + */ + CONNECTION_RATE_EXCEEDED((byte) 0x9F), + /** + * The maximum connection time authorized for this connection has been exceeded. Server. + */ + MAXIMUM_CONNECT_TIME((byte) 0xA0), + /** + * The Server does not support Subscription Identifiers; the subscription is not accepted. Server. + */ + SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), + /** + * The Server does not support Wildcard Subscriptions; the subscription is not accepted. Server. + */ + WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); + + private static final DisconnectReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(DisconnectReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new DisconnectReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static DisconnectReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + private @Getter + final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/PublishAckReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishAckReasonCode.java new file mode 100644 index 00000000..aade08c8 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishAckReasonCode.java @@ -0,0 +1,82 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum PublishAckReasonCode { + /** + * The message is accepted. Publication of the QoS 1 message proceeds. + */ + SUCCESS((byte) 0x00), + /** + * The message is accepted but there are no subscribers. This is sent only by the Server. If the Server knows that + * there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success). + */ + NO_MATCHING_SUBSCRIBERS((byte) 0x10), + + // ERRORS + + /** + * The receiver does not accept the publish but either does not want to reveal the reason, or it does not match one of + * the other values. + */ + UNSPECIFIED_ERROR((byte) 0x80), + /** + * The PUBLISH is valid but the receiver is not willing to accept it. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + /** + * The PUBLISH is not authorized. + */ + NOT_AUTHORIZED((byte) 0x87), + /** + * The Topic Name is not malformed, but is not accepted by this Client or Server. + */ + TOPIC_NAME_INVALID((byte) 0x90), + /** + * The Packet Identifier is already in use. This might indicate a mismatch in the Session State between the Client and + * Server. + */ + PACKET_IDENTIFIER_IN_USE((byte) 0x91), + /** + * An implementation or administrative imposed limit has been exceeded. + */ + QUOTA_EXCEEDED((byte) 0x97), + /** + * The payload format does not match the specified Payload Format Indicator. + */ + PAYLOAD_FORMAT_INVALID((byte) 0x99); + + private static final PublishAckReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(PublishAckReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new PublishAckReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static PublishAckReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/PublishCompletedReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishCompletedReasonCode.java new file mode 100644 index 00000000..81452ed1 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishCompletedReasonCode.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum PublishCompletedReasonCode { + + /** + * Packet Identifier released. Publication of QoS 2 message is complete. + */ + SUCCESS((byte) 0x00), + /** + * The Packet Identifier is not known. This is not an error during recovery, but at other times indicates a mismatch + * between the Session State on the Client and Server. + */ + PACKET_IDENTIFIER_NOT_FOUND((byte) 0x92); + + private static final PublishCompletedReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(PublishCompletedReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new PublishCompletedReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static PublishCompletedReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReceivedReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReceivedReasonCode.java new file mode 100644 index 00000000..fb9e9f73 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReceivedReasonCode.java @@ -0,0 +1,80 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum PublishReceivedReasonCode { + + /** + * The message is accepted. Publication of the QoS 2 message proceeds.. + */ + SUCCESS((byte) 0x00), + /** + * The message is accepted but there are no subscribers. This is sent only by the Server. If the Server knows that + * there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success). + */ + NO_MATCHING_SUBSCRIBERS((byte) 0x10), + /** + * The receiver does not accept the publish but either does not want to reveal the reason, or it does not match one of + * the other values. + */ + UNSPECIFIED_ERROR((byte) 0x80), + /** + * The PUBLISH is valid but the receiver is not willing to accept it. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + /** + * The PUBLISH is not authorized. + */ + NOT_AUTHORIZED((byte) 0x87), + /** + * The Topic Name is not malformed, but is not accepted by this Client or Server. + */ + TOPIC_NAME_INVALID((byte) 0x90), + /** + * The Packet Identifier is already in use. This might indicate a mismatch in the Session State between the Client and + * Server. + */ + PACKET_IDENTIFIER_IN_USE((byte) 0x91), + /** + * An implementation or administrative imposed limit has been exceeded. + */ + QUOTA_EXCEEDED((byte) 0x97), + /** + * The payload format does not match the one specified in the Payload Format Indicator. + */ + PAYLOAD_FORMAT_INVALID((byte) 0x99); + + private static final PublishReceivedReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(PublishReceivedReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new PublishReceivedReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static PublishReceivedReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReleaseReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReleaseReasonCode.java new file mode 100644 index 00000000..20fb3551 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/PublishReleaseReasonCode.java @@ -0,0 +1,50 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum PublishReleaseReasonCode { + + /** + * Message released. + */ + SUCCESS((byte) 0x00), + /** + * The Packet Identifier is not known. This is not an error during recovery, but at other times indicates a mismatch + * between the Session State on the Client and Server. + */ + PACKET_IDENTIFIER_NOT_FOUND((byte) 0x92); + + private static final PublishReleaseReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(PublishReleaseReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new PublishReleaseReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static PublishReleaseReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java new file mode 100644 index 00000000..7541f76c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/SubscribeAckReasonCode.java @@ -0,0 +1,92 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum SubscribeAckReasonCode { + /** + * The subscription is accepted and the maximum QoS sent will be QoS 0. This might be a lower QoS than was requested. + */ + GRANTED_QOS_0((byte) 0x00), + /** + * The subscription is accepted and the maximum QoS sent will be QoS 1. This might be a lower QoS than was requested. + */ + GRANTED_QOS_1((byte) 0x01), + /** + * The subscription is accepted and any received QoS will be sent to this subscription. + */ + GRANTED_QOS_2((byte) 0x02), + + // ERRORS + + /** + * The subscription is not accepted and the Server either does not wish to reveal the reason or none of the other + * Reason Codes apply. + */ + UNSPECIFIED_ERROR((byte) 0x80), + /** + * The SUBSCRIBE is valid but the Server does not accept it. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + /** + * The Client is not authorized to make this subscription. + */ + NOT_AUTHORIZED((byte) 0x87), + /** + * The Topic Filter is correctly formed but is not allowed for this Client. + */ + TOPIC_FILTER_INVALID((byte) 0x8F), + /** + * The specified Packet Identifier is already in use. + */ + PACKET_IDENTIFIER_IN_USE((byte) 0x91), + /** + * An implementation or administrative imposed limit has been exceeded. + */ + QUOTA_EXCEEDED((byte) 0x97), + /** + * The Server does not support Shared Subscriptions for this Client. + */ + SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), + /** + * The Server does not support Subscription Identifiers; the subscription is not accepted. + */ + SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), + /** + * The Server does not support Wildcard Subscriptions; the subscription is not accepted. + */ + WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); + + private static final SubscribeAckReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(SubscribeAckReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new SubscribeAckReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static SubscribeAckReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/UnsubscribeAckReasonCode.java b/model/src/main/java/javasabr/mqtt/model/reason/code/UnsubscribeAckReasonCode.java new file mode 100644 index 00000000..af3c4c3b --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/UnsubscribeAckReasonCode.java @@ -0,0 +1,72 @@ +package javasabr.mqtt.model.reason.code; + +import java.util.stream.Stream; +import javasabr.rlib.common.util.ObjectUtils; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public enum UnsubscribeAckReasonCode { + /** + * The subscription is deleted. + */ + SUCCESS((byte) 0x00), + /** + * The subscription is accepted and the maximum QoS sent will be QoS 1. This might be a lower QoS than was requested. + */ + NO_SUBSCRIPTION_EXISTED((byte) 0x11), + + // ERRORS + + /** + * The unsubscribe could not be completed and the Server either does not wish to reveal the reason or none of the + * other Reason Codes apply. + */ + UNSPECIFIED_ERROR((byte) 0x80), + /** + * The UNSUBSCRIBE is valid but the Server does not accept it. + */ + IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), + /** + * The Client is not authorized to unsubscribe. + */ + NOT_AUTHORIZED((byte) 0x87), + /** + * The Topic Filter is correctly formed but is not allowed for this Client. + */ + TOPIC_FILTER_INVALID((byte) 0x8F), + /** + * The specified Packet Identifier is already in use. + */ + PACKET_IDENTIFIER_IN_USE((byte) 0x91); + + private static final UnsubscribeAckReasonCode[] VALUES; + + static { + + var maxId = Stream + .of(values()) + .mapToInt(UnsubscribeAckReasonCode::getValue) + .map(value -> Byte.toUnsignedInt((byte) value)) + .max() + .orElse(0); + + var values = new UnsubscribeAckReasonCode[maxId + 1]; + + for (var value : values()) { + values[Byte.toUnsignedInt(value.value)] = value; + } + + VALUES = values; + } + + public static UnsubscribeAckReasonCode of(int index) { + return ObjectUtils.notNull( + VALUES[index], + index, + arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg)); + } + + @Getter + private final byte value; +} diff --git a/model/src/main/java/javasabr/mqtt/model/reason/code/package-info.java b/model/src/main/java/javasabr/mqtt/model/reason/code/package-info.java new file mode 100644 index 00000000..672a08f4 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/reason/code/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.reason.code; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/model/SharedSubscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java similarity index 71% rename from src/main/java/com/ss/mqtt/broker/model/SharedSubscriber.java rename to model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java index f82c69af..6df99308 100644 --- a/src/main/java/com/ss/mqtt/broker/model/SharedSubscriber.java +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/SharedSubscriber.java @@ -1,8 +1,7 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.model.topic.SharedTopicFilter; -import com.ss.mqtt.broker.network.client.MqttClient; +package javasabr.mqtt.model.subscriber; +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.topic.SharedTopicFilter; import java.util.Collection; import java.util.concurrent.atomic.AtomicInteger; import javasabr.rlib.collections.array.Array; @@ -33,21 +32,23 @@ public SingleSubscriber getSubscriber() { } public void addSubscriber(SingleSubscriber client) { - subscribers.operations() + subscribers + .operations() .inWriteLock(client, Collection::add); } - public boolean removeSubscriber(MqttClient client) { + public boolean removeSubscriber(MqttUser user) { return subscribers .operations() - .getInWriteLock(client, (singleSubscribers, mqttClient) -> { - int index = singleSubscribers.indexOf(SingleSubscriber::getMqttClient, mqttClient); - if (index >= 0) { - singleSubscribers.remove(index); - return true; - } - return false; - }); + .getInWriteLock( + user, (singleSubscribers, mqttClient) -> { + int index = singleSubscribers.indexOf(SingleSubscriber::getUser, mqttClient); + if (index >= 0) { + singleSubscribers.remove(index); + return true; + } + return false; + }); } public int size() { diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java new file mode 100644 index 00000000..10f4b9bd --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/SingleSubscriber.java @@ -0,0 +1,22 @@ +package javasabr.mqtt.model.subscriber; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.QoS; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@ToString +@EqualsAndHashCode(of = "user") +@RequiredArgsConstructor +public final class SingleSubscriber implements Subscriber { + + @Getter + private final MqttUser user; + private final SubscribeTopicFilter subscribe; + + public QoS getQos() { + return subscribe.getQos(); + } +} diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java b/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java new file mode 100644 index 00000000..568a9109 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/SubscribeTopicFilter.java @@ -0,0 +1,61 @@ +package javasabr.mqtt.model.subscriber; + +import static javasabr.mqtt.model.utils.TopicUtils.buildTopicFilter; + +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.QoS; +import javasabr.mqtt.model.SubscribeRetainHandling; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@EqualsAndHashCode +@RequiredArgsConstructor +public class SubscribeTopicFilter { + + /** + * The subscriber's topic filter. + */ + private final TopicFilter topicFilter; + + /** + * Maximum QoS field. This gives the maximum QoS level at which the Server can send Application Messages to the + * Client. + */ + private final QoS qos; + + /** + * This option specifies whether retained messages are sent when the subscription is established. This does not affect + * the sending of retained messages at any point after the subscribe. If there are no retained messages matching the + * Topic Filter, all of these values act the same. + */ + private final SubscribeRetainHandling retainHandling; + + /** + * If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal to the + * ClientID of the publishing connection. + */ + private final boolean noLocal; + + /** + * If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. If + * false, Application Messages forwarded using this subscription have the RETAIN flag set to 0. Retained messages sent + * when the subscription is established have the RETAIN flag set to 1. + */ + private final boolean retainAsPublished; + + public SubscribeTopicFilter(String topicFilter, QoS qos) { + this(buildTopicFilter(topicFilter), qos, SubscribeRetainHandling.SEND, true, true); + } + + public SubscribeTopicFilter(TopicFilter topicFilter, QoS qos) { + this(topicFilter, qos, SubscribeRetainHandling.SEND, true, true); + } + + @Override + public String toString() { + return "SubscribeTopicFilter(" + "topicFilter=" + topicFilter.getRawTopic() + ", qos=" + qos + ", retainHandling=" + + retainHandling + ", noLocal=" + noLocal + ", retainAsPublished=" + retainAsPublished + ')'; + } +} diff --git a/src/main/java/com/ss/mqtt/broker/model/Subscriber.java b/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java similarity index 67% rename from src/main/java/com/ss/mqtt/broker/model/Subscriber.java rename to model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java index 3b68c313..4953644c 100644 --- a/src/main/java/com/ss/mqtt/broker/model/Subscriber.java +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/Subscriber.java @@ -1,3 +1,3 @@ -package com.ss.mqtt.broker.model; +package javasabr.mqtt.model.subscriber; public sealed interface Subscriber permits SingleSubscriber, SharedSubscriber {} diff --git a/model/src/main/java/javasabr/mqtt/model/subscriber/package-info.java b/model/src/main/java/javasabr/mqtt/model/subscriber/package-info.java new file mode 100644 index 00000000..3caa9faf --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/subscriber/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.subscriber; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/AbstractTopic.java b/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java similarity index 76% rename from src/main/java/com/ss/mqtt/broker/model/topic/AbstractTopic.java rename to model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java index 21b116a2..d757f46e 100644 --- a/src/main/java/com/ss/mqtt/broker/model/topic/AbstractTopic.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/AbstractTopic.java @@ -1,11 +1,10 @@ -package com.ss.mqtt.broker.model.topic; +package javasabr.mqtt.model.topic; -import com.ss.mqtt.broker.util.DebugUtils; +import javasabr.mqtt.base.utils.DebugUtils; +import javasabr.mqtt.model.utils.TopicUtils; import lombok.EqualsAndHashCode; import lombok.Getter; -import static com.ss.mqtt.broker.util.TopicUtils.splitTopic; - @Getter @EqualsAndHashCode(of = "rawTopic") public abstract class AbstractTopic { @@ -20,15 +19,15 @@ public abstract class AbstractTopic { private final String rawTopic; private final int length; - AbstractTopic() { + protected AbstractTopic() { length = 0; segments = EMPTY_ARRAY; rawTopic = EMPTY; } - AbstractTopic(String topicName) { + protected AbstractTopic(String topicName) { length = topicName.length(); - segments = splitTopic(topicName); + segments = TopicUtils.splitTopic(topicName); rawTopic = topicName; } diff --git a/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java b/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java new file mode 100644 index 00000000..f0881965 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/SharedTopicFilter.java @@ -0,0 +1,15 @@ +package javasabr.mqtt.model.topic; + +import lombok.Getter; + +public class SharedTopicFilter extends TopicFilter { + + @Getter + private final String group; + + public SharedTopicFilter(String topicFilter, String group) { + super(topicFilter); + this.group = group; + } +} + diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java new file mode 100644 index 00000000..d047aff8 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicFilter.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.model.topic; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class TopicFilter extends AbstractTopic { + + public TopicFilter(String topicFilter) { + super(topicFilter); + } +} + diff --git a/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java new file mode 100644 index 00000000..caee440c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicName.java @@ -0,0 +1,12 @@ +package javasabr.mqtt.model.topic; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor +public class TopicName extends AbstractTopic { + + public TopicName(String topicName) { + super(topicName); + } +} + diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/TopicSubscribers.java b/model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java similarity index 77% rename from src/main/java/com/ss/mqtt/broker/model/topic/TopicSubscribers.java rename to model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java index 57988386..92e4be15 100644 --- a/src/main/java/com/ss/mqtt/broker/model/topic/TopicSubscribers.java +++ b/model/src/main/java/javasabr/mqtt/model/topic/TopicSubscribers.java @@ -1,16 +1,15 @@ -package com.ss.mqtt.broker.model.topic; +package javasabr.mqtt.model.topic; -import static com.ss.mqtt.broker.util.TopicUtils.*; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.SharedSubscriber; -import com.ss.mqtt.broker.model.SingleSubscriber; -import com.ss.mqtt.broker.model.SubscribeTopicFilter; -import com.ss.mqtt.broker.model.Subscriber; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.util.SubscriberUtils; +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.QoS; import java.util.Objects; import java.util.function.Supplier; +import javasabr.mqtt.model.subscriber.SharedSubscriber; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.SubscribeTopicFilter; +import javasabr.mqtt.model.subscriber.Subscriber; +import javasabr.mqtt.model.utils.SubscriberUtils; +import javasabr.mqtt.model.utils.TopicUtils; import javasabr.rlib.collections.array.Array; import javasabr.rlib.collections.array.ArrayFactory; import javasabr.rlib.collections.array.LockableArray; @@ -29,25 +28,25 @@ public class TopicSubscribers { private static void addSubscriber( LockableArray subscribers, - MqttClient client, + MqttUser user, SubscribeTopicFilter subscribe) { - if (isShared(subscribe.getTopicFilter())) { - addSharedSubscriber(subscribers, client, subscribe); + if (TopicUtils.isShared(subscribe.getTopicFilter())) { + addSharedSubscriber(subscribers, user, subscribe); } else { - addSingleSubscriber(subscribers, client, subscribe); + addSingleSubscriber(subscribers, user, subscribe); } } private static void addSingleSubscriber( LockableArray subscribers, - MqttClient client, + MqttUser user, SubscribeTopicFilter subscribe) { - subscribers.add(new SingleSubscriber(client, subscribe)); + subscribers.add(new SingleSubscriber(user, subscribe)); } private static void addSharedSubscriber( LockableArray subscribers, - MqttClient client, + MqttUser user, SubscribeTopicFilter subscribe) { String group = ((SharedTopicFilter) subscribe.getTopicFilter()).getGroup(); @@ -60,21 +59,21 @@ private static void addSharedSubscriber( subscribers.add(sharedSubscriber); } - var singleSubscriber = new SingleSubscriber(client, subscribe); + var singleSubscriber = new SingleSubscriber(user, subscribe); sharedSubscriber.addSubscriber(singleSubscriber); } - private static boolean removeSubscriber(LockableArray subscribers, TopicFilter topic, MqttClient client) { - return isShared(topic) - ? removeSharedSubscriber(subscribers, ((SharedTopicFilter) topic).getGroup(), client) - : removeSingleSubscriber(subscribers, client); + private static boolean removeSubscriber(LockableArray subscribers, TopicFilter topic, MqttUser user) { + return TopicUtils.isShared(topic) + ? removeSharedSubscriber(subscribers, ((SharedTopicFilter) topic).getGroup(), user) + : removeSingleSubscriber(subscribers, user); } - private static boolean removeSingleSubscriber(LockableArray subscribers, MqttClient client) { + private static boolean removeSingleSubscriber(LockableArray subscribers, MqttUser user) { for (int i = 0, length = subscribers.size(); i < length; i++) { Subscriber subscriber = subscribers.get(i); - MqttClient mqttClient = SubscriberUtils.singleSubscriberToMqttClient(subscriber); - if (Objects.equals(client, mqttClient)) { + MqttUser mqttClient = SubscriberUtils.singleSubscriberToMqttUser(subscriber); + if (Objects.equals(user, mqttClient)) { subscribers.remove(i); return true; } @@ -85,7 +84,7 @@ private static boolean removeSingleSubscriber(LockableArray subscrib private static boolean removeSharedSubscriber( LockableArray subscribers, String group, - MqttClient client) { + MqttUser client) { boolean removed = false; SharedSubscriber sharedSubscriber = (SharedSubscriber) subscribers @@ -164,20 +163,20 @@ private static TopicSubscribers collectSubscribers( private volatile @Getter @Nullable LockableArray subscribers; - public void addSubscriber(MqttClient client, SubscribeTopicFilter subscribe) { - searchPlaceForSubscriber(0, subscribe.getTopicFilter(), client, subscribe); + public void addSubscriber(MqttUser user, SubscribeTopicFilter subscribe) { + searchPlaceForSubscriber(0, subscribe.getTopicFilter(), user, subscribe); } private void searchPlaceForSubscriber( int level, TopicFilter topicFilter, - MqttClient client, + MqttUser user, SubscribeTopicFilter subscribe) { if (level == topicFilter.levelsCount()) { LockableArray subscribers = getOrCreateSubscribers(); subscribers .operations() - .inWriteLock(client, subscribe, TopicSubscribers::addSubscriber); + .inWriteLock(user, subscribe, TopicSubscribers::addSubscriber); } else { LockableRefToRefDictionary topicSubscribers = getOrCreateTopicSubscribers(); TopicSubscribers topicSubscriber = topicSubscribers @@ -188,44 +187,44 @@ private void searchPlaceForSubscriber( MutableRefToRefDictionary::getOrCompute); //noinspection ConstantConditions - topicSubscriber.searchPlaceForSubscriber(level + 1, topicFilter, client, subscribe); + topicSubscriber.searchPlaceForSubscriber(level + 1, topicFilter, user, subscribe); } } - public void removeSubscriber(MqttClient client, SubscribeTopicFilter subscribe) { - removeSubscriber(client, subscribe.getTopicFilter()); + public void removeSubscriber(MqttUser user, SubscribeTopicFilter subscribe) { + removeSubscriber(user, subscribe.getTopicFilter()); } - public boolean removeSubscriber(MqttClient client, TopicFilter topicFilter) { - return searchSubscriberToRemove(0, topicFilter, client); + public boolean removeSubscriber(MqttUser user, TopicFilter topicFilter) { + return searchSubscriberToRemove(0, topicFilter, user); } - private boolean searchSubscriberToRemove(int level, TopicFilter topicFilter, MqttClient mqttClient) { + private boolean searchSubscriberToRemove(int level, TopicFilter topicFilter, MqttUser user) { var removed = false; LockableRefToRefDictionary topicSubscribers = getTopicSubscribers(); if (level == topicFilter.levelsCount()) { - removed = tryToRemoveSubscriber(topicFilter, mqttClient); + removed = tryToRemoveSubscriber(topicFilter, user); } else if (topicSubscribers != null) { TopicSubscribers topicSubscriber = topicSubscribers .operations() .getInReadLock(topicFilter.getSegment(level), Dictionary::get); if (topicSubscriber != null) { - removed = topicSubscriber.searchSubscriberToRemove(level + 1, topicFilter, mqttClient); + removed = topicSubscriber.searchSubscriberToRemove(level + 1, topicFilter, user); } } return removed; } - private boolean tryToRemoveSubscriber(TopicFilter topicFilter, MqttClient mqttClient) { + private boolean tryToRemoveSubscriber(TopicFilter topicFilter, MqttUser user) { LockableArray subscribers = getSubscribers(); if (subscribers == null) { return false; } return subscribers .operations() - .getInWriteLock(topicFilter, mqttClient, TopicSubscribers::removeSubscriber); + .getInWriteLock(topicFilter, user, TopicSubscribers::removeSubscriber); } public Array matches(TopicName topicName) { @@ -234,15 +233,11 @@ public Array matches(TopicName topicName) { return resultArray; } - private void processLevel( - int level, - String segment, - TopicName topicName, - MutableArray result) { + private void processLevel(int level, String segment, TopicName topicName, MutableArray result) { var nextLevel = level + 1; processSegment(nextLevel, segment, topicName, result); - processSegment(nextLevel, SINGLE_LEVEL_WILDCARD, topicName, result); - processSegment(nextLevel, MULTI_LEVEL_WILDCARD, topicName, result); + processSegment(nextLevel, TopicUtils.SINGLE_LEVEL_WILDCARD, topicName, result); + processSegment(nextLevel, TopicUtils.MULTI_LEVEL_WILDCARD, topicName, result); } private void processSegment( diff --git a/model/src/main/java/javasabr/mqtt/model/topic/package-info.java b/model/src/main/java/javasabr/mqtt/model/topic/package-info.java new file mode 100644 index 00000000..fff683e7 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/topic/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.topic; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/model/src/main/java/javasabr/mqtt/model/utils/SubscriberUtils.java b/model/src/main/java/javasabr/mqtt/model/utils/SubscriberUtils.java new file mode 100644 index 00000000..e244920c --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/utils/SubscriberUtils.java @@ -0,0 +1,27 @@ +package javasabr.mqtt.model.utils; + +import javasabr.mqtt.model.MqttUser; +import javasabr.mqtt.model.subscriber.SharedSubscriber; +import javasabr.mqtt.model.subscriber.SingleSubscriber; +import javasabr.mqtt.model.subscriber.Subscriber; +import org.jspecify.annotations.Nullable; + +public class SubscriberUtils { + + private static boolean isSharedSubscriber(Subscriber subscriber) { + return subscriber instanceof SharedSubscriber; + } + + private static boolean isSingleSubscriber(Subscriber subscriber) { + return subscriber instanceof SingleSubscriber; + } + + public static boolean isSharedSubscriberWithGroup(String group, Subscriber subscriber) { + return isSharedSubscriber(subscriber) && group.equals(((SharedSubscriber) subscriber).getGroup()); + } + + @Nullable + public static MqttUser singleSubscriberToMqttUser(Subscriber subscriber) { + return isSingleSubscriber(subscriber) ? ((SingleSubscriber) subscriber).getUser() : null; + } +} diff --git a/src/main/java/com/ss/mqtt/broker/util/TopicUtils.java b/model/src/main/java/javasabr/mqtt/model/utils/TopicUtils.java similarity index 83% rename from src/main/java/com/ss/mqtt/broker/util/TopicUtils.java rename to model/src/main/java/javasabr/mqtt/model/utils/TopicUtils.java index cc8b8811..f7885c3d 100644 --- a/src/main/java/com/ss/mqtt/broker/util/TopicUtils.java +++ b/model/src/main/java/javasabr/mqtt/model/utils/TopicUtils.java @@ -1,9 +1,9 @@ -package com.ss.mqtt.broker.util; +package javasabr.mqtt.model.utils; -import com.ss.mqtt.broker.model.topic.SharedTopicFilter; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.model.topic.TopicName; -import org.springframework.util.StringUtils; +import javasabr.mqtt.model.topic.SharedTopicFilter; +import javasabr.mqtt.model.topic.TopicFilter; +import javasabr.mqtt.model.topic.TopicName; +import javasabr.rlib.common.util.StringUtils; public class TopicUtils { @@ -52,7 +52,7 @@ public static TopicFilter buildTopicFilter(String topicFilter) { } public static String[] splitTopic(String topic) { - int segmentCount = StringUtils.countOccurrencesOf(topic, DELIMITER) + 1; + int segmentCount = countOccurrencesOf(topic, DELIMITER) + 1; var segments = new String[segmentCount]; int i = 0, pos = 0, end; while ((end = topic.indexOf(DELIMITER, pos)) >= 0) { @@ -93,4 +93,20 @@ private static boolean invalid(String topic) { private static boolean isShared(String topicFilter) { return topicFilter.startsWith(SHARE_KEYWORD); } + + public static int countOccurrencesOf(String str, String sub) { + if (StringUtils.isEmpty(str)) { + return 0; + } + + int count = 0; + int pos = 0; + int idx; + while ((idx = str.indexOf(sub, pos)) != -1) { + ++count; + pos = idx + sub.length(); + } + return count; + } + } diff --git a/model/src/main/java/javasabr/mqtt/model/utils/package-info.java b/model/src/main/java/javasabr/mqtt/model/utils/package-info.java new file mode 100644 index 00000000..5f96d001 --- /dev/null +++ b/model/src/main/java/javasabr/mqtt/model/utils/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package javasabr.mqtt.model.utils; + +import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/network/build.gradle b/network/build.gradle new file mode 100644 index 00000000..d996cd8e --- /dev/null +++ b/network/build.gradle @@ -0,0 +1,8 @@ +plugins { + id "java-library" +} + +dependencies { + + testImplementation projects.testSupport +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle index 6dad0b72..0887aaa3 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,3 +1,10 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") rootProject.name = 'MQTT-Broker' + +include(":base") +include(":model") +include(":network") +include(":application") +include(":legacy") +include(":test-support") \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/config/MqttBrokerConfig.java b/src/main/java/com/ss/mqtt/broker/config/MqttBrokerConfig.java deleted file mode 100644 index 0685b791..00000000 --- a/src/main/java/com/ss/mqtt/broker/config/MqttBrokerConfig.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.ss.mqtt.broker.config; - -import com.ss.mqtt.broker.handler.client.DefaultMqttClientReleaseHandler; -import com.ss.mqtt.broker.handler.client.MqttClientReleaseHandler; -import com.ss.mqtt.broker.handler.packet.in.*; -import com.ss.mqtt.broker.handler.publish.in.PublishInHandler; -import com.ss.mqtt.broker.handler.publish.in.Qos0PublishInHandler; -import com.ss.mqtt.broker.handler.publish.in.Qos1PublishInHandler; -import com.ss.mqtt.broker.handler.publish.in.Qos2PublishInHandler; -import com.ss.mqtt.broker.handler.publish.out.PublishOutHandler; -import com.ss.mqtt.broker.handler.publish.out.Qos0PublishOutHandler; -import com.ss.mqtt.broker.handler.publish.out.Qos1PublishOutHandler; -import com.ss.mqtt.broker.handler.publish.out.Qos2PublishOutHandler; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.service.*; -import com.ss.mqtt.broker.service.impl.*; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.jetbrains.annotations.NotNull; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -@Log4j2 -@Configuration -@RequiredArgsConstructor -public class MqttBrokerConfig { - - private final Environment env; - - @Bean - @NotNull ClientIdRegistry clientIdRegistry() { - return new InMemoryClientIdRegistry( - env.getProperty( - "client.id.available.chars", - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_" - ), - env.getProperty("client.id.max.length", int.class, 36) - ); - } - - @Bean - @NotNull MqttSessionService mqttSessionService() { - return new InMemoryMqttSessionService( - env.getProperty("sessions.clean.thread.interval", int.class, 60000) - ); - } - - @Bean - @NotNull CredentialSource credentialSource() { - return new FileCredentialsSource(env.getProperty("credentials.source.file.name", "credentials")); - } - - @Bean - @NotNull AuthenticationService authenticationService(@NotNull CredentialSource credentialSource) { - return new SimpleAuthenticationService( - credentialSource, - env.getProperty("authentication.allow.anonymous", boolean.class, false) - ); - } - - @Bean - PacketInHandler @NotNull [] packetHandlers( - @NotNull AuthenticationService authenticationService, - @NotNull ClientIdRegistry clientIdRegistry, - @NotNull SubscriptionService subscriptionService, - @NotNull PublishingService publishingService, - @NotNull MqttSessionService mqttSessionService - ) { - - var handlers = new PacketInHandler[PacketType.INVALID.ordinal()]; - handlers[PacketType.CONNECT.ordinal()] = new ConnectInPacketHandler( - clientIdRegistry, - authenticationService, - mqttSessionService, - subscriptionService - ); - handlers[PacketType.SUBSCRIBE.ordinal()] = new SubscribeInPacketHandler(subscriptionService); - handlers[PacketType.UNSUBSCRIBE.ordinal()] = new UnsubscribeInPacketHandler(subscriptionService); - handlers[PacketType.PUBLISH.ordinal()] = new PublishInPacketHandler(publishingService); - handlers[PacketType.DISCONNECT.ordinal()] = new DisconnetInPacketHandler(); - handlers[PacketType.PUBLISH_ACK.ordinal()] = new PublishAckInPacketHandler(); - handlers[PacketType.PUBLISH_RECEIVED.ordinal()] = new PublishReceiveInPacketHandler(); - handlers[PacketType.PUBLISH_RELEASED.ordinal()] = new PublishReleaseInPacketHandler(); - handlers[PacketType.PUBLISH_COMPLETED.ordinal()] = new PublishCompleteInPacketHandler(); - - return handlers; - } - - @Bean - @NotNull MqttClientReleaseHandler mqttClientReleaseHandler( - @NotNull ClientIdRegistry clientIdRegistry, - @NotNull MqttSessionService mqttSessionService, - @NotNull SubscriptionService subscriptionService - ) { - return new DefaultMqttClientReleaseHandler( - clientIdRegistry, - mqttSessionService, - subscriptionService - ); - } - - @Bean - @NotNull SubscriptionService subscriptionService() { - return new SimpleSubscriptionService(); - } - - @Bean - @NotNull PublishOutHandler[] publishOutHandlers() { - return new PublishOutHandler[] { - new Qos0PublishOutHandler(), - new Qos1PublishOutHandler(), - new Qos2PublishOutHandler(), - }; - } - - @Bean - @NotNull PublishInHandler[] publishInHandlers( - @NotNull SubscriptionService subscriptionService, - @NotNull PublishOutHandler[] publishOutHandlers - ) { - return new PublishInHandler[] { - new Qos0PublishInHandler(subscriptionService, publishOutHandlers), - new Qos1PublishInHandler(subscriptionService, publishOutHandlers), - new Qos2PublishInHandler(subscriptionService, publishOutHandlers), - }; - } - - @Bean - @NotNull PublishingService publishingService(@NotNull PublishInHandler[] publishInHandlers) { - return new DefaultPublishingService(publishInHandlers); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/config/MqttConnectionConfig.java b/src/main/java/com/ss/mqtt/broker/config/MqttConnectionConfig.java deleted file mode 100644 index 8ee8fb2f..00000000 --- a/src/main/java/com/ss/mqtt/broker/config/MqttConnectionConfig.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.ss.mqtt.broker.config; - -import com.ss.mqtt.broker.model.QoS; -import lombok.Data; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@Getter -@RequiredArgsConstructor -public class MqttConnectionConfig { - - private final QoS maxQos; - - private final int maximumPacketSize; - private final int minKeepAliveTime; - private final int receiveMaximum; - private final int topicAliasMaximum; - - private final long defaultSessionExpiryInterval; - - private final boolean keepAliveEnabled; - private final boolean sessionsEnabled; - private final boolean retainAvailable; - private final boolean wildcardSubscriptionAvailable; - private final boolean subscriptionIdAvailable; - private final boolean sharedSubscriptionAvailable; -} diff --git a/src/main/java/com/ss/mqtt/broker/config/MqttNetworkConfig.java b/src/main/java/com/ss/mqtt/broker/config/MqttNetworkConfig.java deleted file mode 100644 index 1295432b..00000000 --- a/src/main/java/com/ss/mqtt/broker/config/MqttNetworkConfig.java +++ /dev/null @@ -1,334 +0,0 @@ -package com.ss.mqtt.broker.config; - -import com.ss.mqtt.broker.handler.client.MqttClientReleaseHandler; -import com.ss.mqtt.broker.handler.packet.in.PacketInHandler; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.client.ExternalMqttClient; -import com.ss.mqtt.broker.network.client.InternalMqttClient; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import javasabr.rlib.network.BufferAllocator; -import javasabr.rlib.network.Network; -import javasabr.rlib.network.NetworkFactory; -import javasabr.rlib.network.ServerNetworkConfig; -import javasabr.rlib.network.ServerNetworkConfig.SimpleServerNetworkConfig; -import javasabr.rlib.network.impl.DefaultBufferAllocator; -import javasabr.rlib.network.server.ServerNetwork; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.core.env.Environment; - -import java.net.InetSocketAddress; -import java.nio.channels.AsynchronousSocketChannel; -import java.util.function.BiFunction; -import java.util.function.Consumer; - -@Log4j2 -@Configuration -@RequiredArgsConstructor -public class MqttNetworkConfig { - - private interface ChannelFactory extends - BiFunction, AsynchronousSocketChannel, MqttConnection> {} - - private final Environment env; - - @Bean - ServerNetworkConfig internalNetworkConfig() { - return SimpleServerNetworkConfig - .builder() - .readBufferSize(env.getProperty("mqtt.internal.network.read.buffer.size", int.class, 2048)) - .pendingBufferSize(env.getProperty("mqtt.internal.network.pending.buffer.size", int.class, 4096)) - .writeBufferSize(env.getProperty("mqtt.internal.network.write.buffer.size", int.class, 2048)) - .threadGroupName("InternalNetwork") - .threadGroupSize(env.getProperty("mqtt.internal.network.thread.count", int.class, 1)) - .build(); - } - - @Bean - ServerNetworkConfig externalNetworkConfig() { - return SimpleServerNetworkConfig - .builder() - .readBufferSize(env.getProperty("mqtt.external.network.read.buffer.size", int.class, 100)) - .pendingBufferSize(env.getProperty("mqtt.external.network.pending.buffer.size", int.class, 200)) - .writeBufferSize(env.getProperty("mqtt.external.network.write.buffer.size", int.class, 100)) - .threadGroupName("ExternalNetwork") - .threadGroupSize(env.getProperty("mqtt.external.network.thread.count", int.class, 1)) - .build(); - } - - @Bean - BufferAllocator internalBufferAllocator(ServerNetworkConfig internalNetworkConfig) { - return new DefaultBufferAllocator(internalNetworkConfig); - } - - @Bean - BufferAllocator externalBufferAllocator(ServerNetworkConfig externalNetworkConfig) { - return new DefaultBufferAllocator(externalNetworkConfig); - } - - @Bean - ServerNetwork externalNetwork( - ServerNetworkConfig externalNetworkConfig, - BufferAllocator externalBufferAllocator, - MqttConnectionConfig externalConnectionConfig, - PacketInHandler[] packetHandlers, - MqttClientReleaseHandler mqttClientReleaseHandler - ) { - return NetworkFactory.newServerNetwork( - externalNetworkConfig, - externalConnectionFactory( - externalBufferAllocator, - externalConnectionConfig, - packetHandlers, - mqttClientReleaseHandler - ) - ); - } - - @Bean - ServerNetwork internalNetwork( - ServerNetworkConfig internalNetworkConfig, - BufferAllocator internalBufferAllocator, - MqttConnectionConfig internalConnectionConfig, - PacketInHandler[] packetHandlers, - MqttClientReleaseHandler mqttClientReleaseHandler - ) { - return NetworkFactory.newServerNetwork( - internalNetworkConfig, - internalConnectionFactory( - internalBufferAllocator, - internalConnectionConfig, - packetHandlers, - mqttClientReleaseHandler - ) - ); - } - - @Bean - InetSocketAddress externalNetworkAddress( - ServerNetwork externalNetwork, - Consumer externalConnectionConsumer - ) { - - var address = new InetSocketAddress( - env.getProperty("mqtt.external.network.host", "localhost"), - env.getProperty("mqtt.external.network.port", int.class, 1883) - ); - - externalNetwork.start(address); - externalNetwork.onAccept(externalConnectionConsumer); - - return address; - } - - @Bean - InetSocketAddress internalNetworkAddress( - ServerNetwork internalNetwork, - Consumer internalConnectionConsumer - ) { - - var address = new InetSocketAddress( - env.getProperty("mqtt.internal.network.host", "localhost"), - env.getProperty("mqtt.internal.network.port", int.class, 11883) - ); - - internalNetwork.start(address); - internalNetwork.onAccept(internalConnectionConsumer); - - return address; - } - - @Bean - Consumer externalConnectionConsumer() { - return mqttConnection -> { - log.info("Accepted external connection: {}", mqttConnection); - var client = (UnsafeMqttClient) mqttConnection.getClient(); - mqttConnection.onReceive((conn, packet) -> client.handle(packet)); - }; - } - - @Bean - Consumer internalConnectionConsumer() { - return mqttConnection -> { - log.info("Accepted internal connection: {}", mqttConnection); - var client = (UnsafeMqttClient) mqttConnection.getClient(); - mqttConnection.onReceive((conn, packet) -> client.handle(packet)); - }; - } - - @Bean - MqttConnectionConfig externalConnectionConfig() { - return new MqttConnectionConfig( - QoS.of(env.getProperty("mqtt.connection.max.qos", int.class, 2)), - env.getProperty( - "mqtt.external.connection.max.packet.size", - int.class, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.min.keep.alive", - int.class, - MqttPropertyConstants.SERVER_KEEP_ALIVE_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.receive.maximum", - int.class, - MqttPropertyConstants.RECEIVE_MAXIMUM_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.topic.alias.maximum", - int.class, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_DISABLED - ), - env.getProperty( - "mqtt.external.connection.default.session.expiration.time", - long.class, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.keep.alive.enabled", - boolean.class, - MqttPropertyConstants.KEEP_ALIVE_ENABLED_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.sessions.enabled", - boolean.class, - MqttPropertyConstants.SESSIONS_ENABLED_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.retain.available", - boolean.class, - MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.wildcard.subscription.available", - boolean.class, - MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.subscription.id.available", - boolean.class, - MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.external.connection.shared.subscription.available", - boolean.class, - MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - ) - ); - } - - @Bean - MqttConnectionConfig internalConnectionConfig() { - return new MqttConnectionConfig( - QoS.of(env.getProperty("mqtt.internal.connection.max.qos", int.class, 2)), - env.getProperty( - "mqtt.internal.connection.max.packet.size", - int.class, - MqttPropertyConstants.MAXIMUM_PACKET_SIZE_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.min.keep.alive", - int.class, - MqttPropertyConstants.SERVER_KEEP_ALIVE_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.receive.maximum", - int.class, - MqttPropertyConstants.RECEIVE_MAXIMUM_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.topic.alias.maximum", - int.class, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_DISABLED - ), - env.getProperty( - "mqtt.internal.connection.default.session.expiration.time", - long.class, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.keep.alive.enabled", - boolean.class, - MqttPropertyConstants.KEEP_ALIVE_ENABLED_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.sessions.enabled", - boolean.class, - MqttPropertyConstants.SESSIONS_ENABLED_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.retain.available", - boolean.class, - MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.wildcard.subscription.available", - boolean.class, - MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.subscription.id.available", - boolean.class, - MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - ), - env.getProperty( - "mqtt.internal.connection.shared.subscription.available", - boolean.class, - MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - ) - ); - } - - private ChannelFactory externalConnectionFactory( - BufferAllocator bufferAllocator, - MqttConnectionConfig connectionConfig, - PacketInHandler[] packetHandlers, - MqttClientReleaseHandler releaseHandler - ) { - return connectionFactory( - bufferAllocator, - connectionConfig, - packetHandlers, - releaseHandler, - ExternalMqttClient::new - ); - } - - private ChannelFactory internalConnectionFactory( - BufferAllocator bufferAllocator, - MqttConnectionConfig connectionConfig, - PacketInHandler[] packetHandlers, - MqttClientReleaseHandler releaseHandler - ) { - return connectionFactory( - bufferAllocator, - connectionConfig, - packetHandlers, - releaseHandler, - InternalMqttClient::new - ); - } - - private ChannelFactory connectionFactory( - BufferAllocator bufferAllocator, - MqttConnectionConfig connectionConfig, - PacketInHandler[] packetHandlers, - MqttClientReleaseHandler releaseHandler, - BiFunction clientFactory - ) { - return (network, channel) -> new MqttConnection( - network, - channel, - bufferAllocator, - 100, - packetHandlers, - connectionConfig, - mqttConnection -> clientFactory.apply(mqttConnection, releaseHandler) - ); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/exception/ConnectionRejectException.java b/src/main/java/com/ss/mqtt/broker/exception/ConnectionRejectException.java deleted file mode 100644 index f5c20453..00000000 --- a/src/main/java/com/ss/mqtt/broker/exception/ConnectionRejectException.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ss.mqtt.broker.exception; - -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import lombok.Getter; - -public class ConnectionRejectException extends MqttException { - - private final @Getter ConnectAckReasonCode reasonCode; - - public ConnectionRejectException(ConnectAckReasonCode reasonCode) { - this.reasonCode = reasonCode; - } - - public ConnectionRejectException(Throwable cause, ConnectAckReasonCode reasonCode) { - super(cause); - this.reasonCode = reasonCode; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/exception/CredentialsSourceException.java b/src/main/java/com/ss/mqtt/broker/exception/CredentialsSourceException.java deleted file mode 100644 index 5293d74b..00000000 --- a/src/main/java/com/ss/mqtt/broker/exception/CredentialsSourceException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ss.mqtt.broker.exception; - -public class CredentialsSourceException extends RuntimeException { - - public CredentialsSourceException(String message) { - super(message); - } - - public CredentialsSourceException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/exception/InconsistentSubscriptionStateException.java b/src/main/java/com/ss/mqtt/broker/exception/InconsistentSubscriptionStateException.java deleted file mode 100644 index 1ad665b0..00000000 --- a/src/main/java/com/ss/mqtt/broker/exception/InconsistentSubscriptionStateException.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ss.mqtt.broker.exception; - -public class InconsistentSubscriptionStateException extends RuntimeException { - - public InconsistentSubscriptionStateException(String message) { - super(message); - } - - public InconsistentSubscriptionStateException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/exception/MqttException.java b/src/main/java/com/ss/mqtt/broker/exception/MqttException.java deleted file mode 100644 index 73226246..00000000 --- a/src/main/java/com/ss/mqtt/broker/exception/MqttException.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ss.mqtt.broker.exception; - -public class MqttException extends RuntimeException { - - public MqttException() { - } - - public MqttException(String message) { - super(message); - } - - public MqttException(Throwable cause) { - super(cause); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt311PacketOutFactory.java b/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt311PacketOutFactory.java deleted file mode 100644 index 02d8d957..00000000 --- a/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt311PacketOutFactory.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.ss.mqtt.broker.factory.packet.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.out.ConnectAck311OutPacket; -import com.ss.mqtt.broker.network.packet.out.Disconnect311OutPacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.network.packet.out.PingRequest311OutPacket; -import com.ss.mqtt.broker.network.packet.out.PingResponse311OutPacket; -import com.ss.mqtt.broker.network.packet.out.Publish311OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishAck311OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishComplete311OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishOutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishReceived311OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishRelease311OutPacket; -import com.ss.mqtt.broker.network.packet.out.SubscribeAck311OutPacket; -import com.ss.mqtt.broker.network.packet.out.UnsubscribeAck311OutPacket; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.MutableArray; - -public class Mqtt311PacketOutFactory extends MqttPacketOutFactory { - - @Override - public MqttWritablePacket newConnectAck( - MqttClient client, - ConnectAckReasonCode reasonCode, - boolean sessionPresent, - String requestedClientId, - long requestedSessionExpiryInterval, - int requestedKeepAlive, - int requestedReceiveMax, - String reason, - String serverReference, - String responseInformation, - String authenticationMethod, - byte[] authenticationData, - MutableArray userProperties - ) { - return new ConnectAck311OutPacket(reasonCode, sessionPresent); - } - - @Override - public PublishOutPacket newPublish( - int packetId, - QoS qos, - boolean retained, - boolean duplicate, - String topicName, - int topicAlias, - byte[] payload, - boolean stringPayload, - String responseTopic, - byte[] correlationData, - MutableArray userProperties - ) { - return new Publish311OutPacket( - packetId, - qos, - retained, - duplicate, - topicName, - payload - ); - } - - @Override - public MqttWritablePacket newPublishAck( - int packetId, - PublishAckReasonCode reasonCode, - String reason, - MutableArray userProperties - ) { - return new PublishAck311OutPacket(packetId); - } - - @Override - public MqttWritablePacket newSubscribeAck( - int packetId, - Array reasonCodes, - String reason, - MutableArray userProperties - ) { - return new SubscribeAck311OutPacket(reasonCodes, packetId); - } - - @Override - public MqttWritablePacket newUnsubscribeAck( - int packetId, - Array reasonCodes, - MutableArray userProperties, - String reason - ) { - return new UnsubscribeAck311OutPacket(packetId); - } - - @Override - public MqttWritablePacket newDisconnect( - MqttClient client, - DisconnectReasonCode reasonCode, - MutableArray userProperties, - String reason, - String serverReference - ) { - return new Disconnect311OutPacket(); - } - - @Override - public MqttWritablePacket newAuthenticate( - AuthenticateReasonCode reasonCode, - String authenticateMethod, - byte[] authenticateData, - MutableArray userProperties, - String reason - ) { - throw new UnsupportedOperationException(); - } - - @Override - public MqttWritablePacket newPingRequest() { - return new PingRequest311OutPacket(); - } - - @Override - public MqttWritablePacket newPingResponse() { - return new PingResponse311OutPacket(); - } - - @Override - public MqttWritablePacket newPublishRelease( - int packetId, - PublishReleaseReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishRelease311OutPacket(packetId); - } - - @Override - public MqttWritablePacket newPublishReceived( - int packetId, - PublishReceivedReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishReceived311OutPacket(packetId); - } - - @Override - public MqttWritablePacket newPublishCompleted( - int packetId, - PublishCompletedReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishComplete311OutPacket(packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt5PacketOutFactory.java b/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt5PacketOutFactory.java deleted file mode 100644 index a20e99d5..00000000 --- a/src/main/java/com/ss/mqtt/broker/factory/packet/out/Mqtt5PacketOutFactory.java +++ /dev/null @@ -1,198 +0,0 @@ -package com.ss.mqtt.broker.factory.packet.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.out.Authentication5OutPacket; -import com.ss.mqtt.broker.network.packet.out.ConnectAck5OutPacket; -import com.ss.mqtt.broker.network.packet.out.Disconnect5OutPacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.network.packet.out.Publish5OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishAck5OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishComplete5OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishOutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishReceived5OutPacket; -import com.ss.mqtt.broker.network.packet.out.PublishRelease5OutPacket; -import com.ss.mqtt.broker.network.packet.out.SubscribeAck5OutPacket; -import com.ss.mqtt.broker.network.packet.out.UnsubscribeAck5OutPacket; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.MutableArray; - -public class Mqtt5PacketOutFactory extends Mqtt311PacketOutFactory { - - @Override - public MqttWritablePacket newConnectAck( - MqttClient client, - ConnectAckReasonCode reasonCode, - boolean sessionPresent, - String requestedClientId, - long requestedSessionExpiryInterval, - int requestedKeepAlive, - int requestedReceiveMax, - String reason, - String serverReference, - String responseInformation, - String authenticationMethod, - byte[] authenticationData, - MutableArray userProperties - ) { - var config = client.getConnectionConfig(); - return new ConnectAck5OutPacket( - reasonCode, - sessionPresent, - requestedClientId, - requestedSessionExpiryInterval, - requestedKeepAlive, - requestedReceiveMax, - reason, - serverReference, - responseInformation, - authenticationMethod, - authenticationData, - userProperties, - client.getClientId(), - config.getMaxQos(), - client.getSessionExpiryInterval(), - client.getMaximumPacketSize(), - client.getReceiveMax(), - client.getTopicAliasMaximum(), - client.getKeepAlive(), - config.isRetainAvailable(), - config.isWildcardSubscriptionAvailable(), - config.isSubscriptionIdAvailable(), - config.isSharedSubscriptionAvailable() - ); - } - - @Override - public PublishOutPacket newPublish( - int packetId, - QoS qos, - boolean retained, - boolean duplicate, - String topicName, - int topicAlias, - byte[] payload, - boolean stringPayload, - String responseTopic, - byte[] correlationData, - MutableArray userProperties - ) { - return new Publish5OutPacket( - packetId, - qos, - retained, - duplicate, - topicName, - payload, - topicAlias, - stringPayload, - responseTopic, - correlationData, - userProperties - ); - } - - @Override - public MqttWritablePacket newPublishAck( - int packetId, - PublishAckReasonCode reasonCode, - String reason, - MutableArray userProperties - ) { - return new PublishAck5OutPacket(packetId, reasonCode, userProperties, reason); - } - - @Override - public MqttWritablePacket newSubscribeAck( - int packetId, - Array reasonCodes, - String reason, - MutableArray userProperties - ) { - return new SubscribeAck5OutPacket(packetId, reasonCodes, userProperties, reason); - } - - @Override - public MqttWritablePacket newUnsubscribeAck( - int packetId, - Array reasonCodes, - MutableArray userProperties, - String reason - ) { - return new UnsubscribeAck5OutPacket(packetId, reasonCodes, userProperties, reason); - } - - @Override - public MqttWritablePacket newDisconnect( - MqttClient client, - DisconnectReasonCode reasonCode, - MutableArray userProperties, - String reason, - String serverReference - ) { - return new Disconnect5OutPacket( - reasonCode, - userProperties, - reason, - serverReference, - client.getSessionExpiryInterval() - ); - } - - @Override - public MqttWritablePacket newAuthenticate( - AuthenticateReasonCode reasonCode, - String authenticateMethod, - byte[] authenticateData, - MutableArray userProperties, - String reason - ) { - return new Authentication5OutPacket( - userProperties, - reasonCode, - reason, - authenticateMethod, - authenticateData - ); - } - - @Override - public MqttWritablePacket newPublishRelease( - int packetId, - PublishReleaseReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishRelease5OutPacket(packetId, reasonCode, userProperties, reason); - } - - @Override - public MqttWritablePacket newPublishReceived( - int packetId, - PublishReceivedReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishReceived5OutPacket(packetId, reasonCode, userProperties, reason); - } - - @Override - public MqttWritablePacket newPublishCompleted( - int packetId, - PublishCompletedReasonCode reasonCode, - MutableArray userProperties, - String reason - ) { - return new PublishComplete5OutPacket(packetId, reasonCode, userProperties, reason); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/factory/packet/out/MqttPacketOutFactory.java b/src/main/java/com/ss/mqtt/broker/factory/packet/out/MqttPacketOutFactory.java deleted file mode 100644 index e95c32a7..00000000 --- a/src/main/java/com/ss/mqtt/broker/factory/packet/out/MqttPacketOutFactory.java +++ /dev/null @@ -1,245 +0,0 @@ -package com.ss.mqtt.broker.factory.packet.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.network.packet.out.PublishOutPacket; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.StringUtils; - -public abstract class MqttPacketOutFactory { - - public abstract MqttWritablePacket newConnectAck( - MqttClient client, - ConnectAckReasonCode reasonCode, - boolean sessionPresent, - String requestedClientId, - long requestedSessionExpiryInterval, - int requestedKeepAlive, - int requestedReceiveMax, - String reason, - String serverReference, - String responseInformation, - String authenticationMethod, - byte[] authenticationData, - MutableArray userProperties - ); - - public MqttWritablePacket newConnectAck( - MqttClient client, - ConnectAckReasonCode reasonCode, - boolean sessionPresent, - String requestedClientId, - long requestedSessionExpiryInterval, - int requestedKeepAlive, - int requestedReceiveMax - ) { - return newConnectAck( - client, - reasonCode, - sessionPresent, - requestedClientId, - requestedSessionExpiryInterval, - requestedKeepAlive, - requestedReceiveMax, - StringUtils.EMPTY, - StringUtils.EMPTY, - StringUtils.EMPTY, - StringUtils.EMPTY, - ArrayUtils.EMPTY_BYTE_ARRAY, - MutableArray.ofType(StringPair.class) - ); - } - - public MqttWritablePacket newConnectAck( - MqttClient client, - ConnectAckReasonCode reasonCode - ) { - return newConnectAck( - client, - reasonCode, - false, - StringUtils.EMPTY, - client.getSessionExpiryInterval(), - client.getKeepAlive(), - client.getReceiveMax(), - StringUtils.EMPTY, - StringUtils.EMPTY, - StringUtils.EMPTY, - StringUtils.EMPTY, - ArrayUtils.EMPTY_BYTE_ARRAY, - MutableArray.ofType(StringPair.class) - ); - } - - public PublishOutPacket newPublish( - int packetId, - QoS qos, - boolean retained, - boolean duplicate, - String topicName, - byte[] payload - ) { - return newPublish( - packetId, - qos, - retained, - duplicate, - topicName, - 0, - payload, - false, - StringUtils.EMPTY, - ArrayUtils.EMPTY_BYTE_ARRAY, - MutableArray.ofType(StringPair.class) - ); - } - - public abstract PublishOutPacket newPublish( - int packetId, - QoS qos, - boolean retained, - boolean duplicate, - String topicName, - int topicAlias, - byte[] payload, - boolean stringPayload, - String responseTopic, - byte[] correlationData, - MutableArray userProperties - ); - - public abstract MqttWritablePacket newPublishAck( - int packetId, - PublishAckReasonCode reasonCode, - String reason, - MutableArray userProperties - ); - - public MqttWritablePacket newPublishAck( - int packetId, - PublishAckReasonCode reasonCode - ) { - return newPublishAck(packetId, reasonCode, StringUtils.EMPTY, MutableArray.ofType(StringPair.class)); - } - - public abstract MqttWritablePacket newSubscribeAck( - int packetId, - Array reasonCodes, - String reason, - MutableArray userProperties - ); - - public MqttWritablePacket newSubscribeAck( - int packetId, - Array reasonCodes - ) { - return newSubscribeAck(packetId, reasonCodes, StringUtils.EMPTY, MutableArray.ofType(StringPair.class)); - } - - public abstract MqttWritablePacket newUnsubscribeAck( - int packetId, - Array reasonCodes, - MutableArray userProperties, - String reason - ); - - public MqttWritablePacket newUnsubscribeAck( - int packetId, - Array reasonCodes - ) { - return newUnsubscribeAck(packetId, reasonCodes, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); - } - - public abstract MqttWritablePacket newDisconnect( - MqttClient client, - DisconnectReasonCode reasonCode, - MutableArray userProperties, - String reason, - String serverReference - ); - - public MqttWritablePacket newDisconnect(MqttClient client, DisconnectReasonCode reasonCode) { - return newDisconnect(client, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY, StringUtils.EMPTY); - } - - public abstract MqttWritablePacket newAuthenticate( - AuthenticateReasonCode reasonCode, - String authenticateMethod, - byte[] authenticateData, - MutableArray userProperties, - String reason - ); - - public MqttWritablePacket newAuthenticate( - AuthenticateReasonCode reasonCode, - String authenticateMethod, - byte[] authenticateData - ) { - return newAuthenticate( - reasonCode, - authenticateMethod, - authenticateData, - MutableArray.ofType(StringPair.class), - StringUtils.EMPTY - ); - } - - public abstract MqttWritablePacket newPingRequest(); - - public abstract MqttWritablePacket newPingResponse(); - - public abstract MqttWritablePacket newPublishRelease( - int packetId, - PublishReleaseReasonCode reasonCode, - MutableArray userProperties, - String reason - ); - - public MqttWritablePacket newPublishRelease( - int packetId, - PublishReleaseReasonCode reasonCode - ) { - return newPublishRelease(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); - } - - public abstract MqttWritablePacket newPublishReceived( - int packetId, - PublishReceivedReasonCode reasonCode, - MutableArray userProperties, - String reason - ); - - public MqttWritablePacket newPublishReceived( - int packetId, - PublishReceivedReasonCode reasonCode - ) { - return newPublishReceived(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); - } - - public abstract MqttWritablePacket newPublishCompleted( - int packetId, - PublishCompletedReasonCode reasonCode, - MutableArray userProperties, - String reason - ); - - public MqttWritablePacket newPublishCompleted( - int packetId, - PublishCompletedReasonCode reasonCode - ) { - return newPublishCompleted(packetId, reasonCode, MutableArray.ofType(StringPair.class), StringUtils.EMPTY); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/client/AbstractMqttClientReleaseHandler.java b/src/main/java/com/ss/mqtt/broker/handler/client/AbstractMqttClientReleaseHandler.java deleted file mode 100644 index 7438dce6..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/client/AbstractMqttClientReleaseHandler.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.ss.mqtt.broker.handler.client; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.client.AbstractMqttClient; -import com.ss.mqtt.broker.service.ClientIdRegistry; -import com.ss.mqtt.broker.service.MqttSessionService; -import com.ss.mqtt.broker.service.SubscriptionService; -import javasabr.rlib.common.util.StringUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import reactor.core.publisher.Mono; - -@Log4j2 -@RequiredArgsConstructor -public abstract class AbstractMqttClientReleaseHandler implements - MqttClientReleaseHandler { - - private final ClientIdRegistry clientIdRegistry; - private final MqttSessionService sessionService; - private final SubscriptionService subscriptionService; - - @Override - public Mono release(UnsafeMqttClient client) { - var clientId = client.getClientId(); - //noinspection unchecked - return releaseImpl((T) client) - .doOnNext(aVoid -> log.info("Client {} was released", clientId)); - } - - protected Mono releaseImpl(T client) { - - var clientId = client.getClientId(); - client.setClientId(StringUtils.EMPTY); - - if (StringUtils.isEmpty(clientId)) { - log.warn("This client {} is already released or rejected", client); - return Mono.empty(); - } - - var session = client.getSession(); - - Mono asyncActions = null; - - if (session != null) { - subscriptionService.cleanSubscriptions(client, session); - if (client.getConnectionConfig().isSessionsEnabled()) { - asyncActions = sessionService.store(clientId, session, client.getSessionExpiryInterval()); - client.setSession(null); - } - } - - if (asyncActions != null) { - asyncActions = asyncActions.flatMap(any -> clientIdRegistry.unregister(clientId)); - } else { - asyncActions = clientIdRegistry.unregister(clientId); - } - - return asyncActions; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/client/DefaultMqttClientReleaseHandler.java b/src/main/java/com/ss/mqtt/broker/handler/client/DefaultMqttClientReleaseHandler.java deleted file mode 100644 index 16f0d209..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/client/DefaultMqttClientReleaseHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.handler.client; - -import com.ss.mqtt.broker.network.client.ExternalMqttClient; -import com.ss.mqtt.broker.service.ClientIdRegistry; -import com.ss.mqtt.broker.service.MqttSessionService; -import com.ss.mqtt.broker.service.SubscriptionService; - -public class DefaultMqttClientReleaseHandler extends AbstractMqttClientReleaseHandler { - - public DefaultMqttClientReleaseHandler( - ClientIdRegistry clientIdRegistry, - MqttSessionService sessionService, - SubscriptionService subscriptionService - ) { - super(clientIdRegistry, sessionService, subscriptionService); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/client/MqttClientReleaseHandler.java b/src/main/java/com/ss/mqtt/broker/handler/client/MqttClientReleaseHandler.java deleted file mode 100644 index f3830691..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/client/MqttClientReleaseHandler.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.ss.mqtt.broker.handler.client; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import reactor.core.publisher.Mono; - -public interface MqttClientReleaseHandler { - - Mono release(UnsafeMqttClient client); -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/AbstractPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/AbstractPacketHandler.java deleted file mode 100644 index a21e34bf..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/AbstractPacketHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; - -public abstract class AbstractPacketHandler implements - PacketInHandler { - - @Override - public void handle(UnsafeMqttClient client, MqttReadablePacket packet) { - //noinspection unchecked - handleImpl((C) client, (R) packet); - } - - protected abstract void handleImpl(C client, R packet); -} - diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/ConnectInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/ConnectInPacketHandler.java deleted file mode 100644 index c9eebf80..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/ConnectInPacketHandler.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import static com.ss.mqtt.broker.model.MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.SERVER_KEEP_ALIVE_DISABLED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DISABLED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_DISABLED; -import static com.ss.mqtt.broker.model.MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED; -import static com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD; -import static com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID; -import static com.ss.mqtt.broker.util.ReactorUtils.ifTrue; -import com.ss.mqtt.broker.exception.ConnectionRejectException; -import com.ss.mqtt.broker.exception.MalformedPacketMqttException; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.ConnectInPacket; -import com.ss.mqtt.broker.service.AuthenticationService; -import com.ss.mqtt.broker.service.ClientIdRegistry; -import com.ss.mqtt.broker.service.MqttSessionService; -import com.ss.mqtt.broker.service.SubscriptionService; -import javasabr.rlib.common.util.StringUtils; -import lombok.RequiredArgsConstructor; -import lombok.extern.log4j.Log4j2; -import reactor.core.publisher.Mono; - -@Log4j2 -@RequiredArgsConstructor -public class ConnectInPacketHandler extends AbstractPacketHandler { - - private final ClientIdRegistry clientIdRegistry; - private final AuthenticationService authenticationService; - private final MqttSessionService mqttSessionService; - private final SubscriptionService subscriptionService; - - @Override - protected void handleImpl(UnsafeMqttClient client, ConnectInPacket packet) { - - var connection = client.getConnection(); - connection.setMqttVersion(packet.getMqttVersion()); - - if (checkPacketException(client, packet)) { - return; - } - - authenticationService.auth(packet.getUsername(), packet.getPassword()) - .flatMap(ifTrue(client, packet, this::registerClient, BAD_USER_NAME_OR_PASSWORD, client::reject)) - .flatMap(ifTrue(client, packet, this::restoreSession, CLIENT_IDENTIFIER_NOT_VALID, client::reject)) - .subscribe(); - } - - private Mono registerClient( - UnsafeMqttClient client, - ConnectInPacket packet - ) { - - var requestedClientId = packet.getClientId(); - - if (StringUtils.isNotEmpty(requestedClientId)) { - return clientIdRegistry.register(requestedClientId) - .map(ifTrue(requestedClientId, client::setClientId)); - } else { - - var mqttVersion = client - .getConnection() - .getMqttVersion(); - - // we can't assign generated client if for mqtt version less than 5 - if (mqttVersion.ordinal() < MqttVersion.MQTT_5.ordinal()) { - return Mono.just(false); - } - - return clientIdRegistry.generate() - .flatMap(newClientId -> clientIdRegistry.register(newClientId) - .map(ifTrue(newClientId, client::setClientId))); - } - } - - private Mono restoreSession( - UnsafeMqttClient client, - ConnectInPacket packet - ) { - - if (packet.isCleanStart()) { - return mqttSessionService.create(client.getClientId()) - .flatMap(session -> onConnected(client, packet, session, false)); - } else { - return mqttSessionService.restore(client.getClientId()) - .flatMap(session -> onConnected(client, packet, session, true)) - .switchIfEmpty(Mono.defer(() -> mqttSessionService.create(client.getClientId()) - .flatMap(session -> onConnected(client, packet, session, false)))); - } - } - - private Mono onConnected( - UnsafeMqttClient client, - ConnectInPacket packet, - MqttSession session, - boolean sessionRestored - ) { - - var connection = client.getConnection(); - var config = connection.getConfig(); - - // if it was closed in parallel - if (connection.isClosed() && config.isSessionsEnabled()) { - // store the session again - return mqttSessionService.store(client.getClientId(), session, config.getDefaultSessionExpiryInterval()); - } - - // select result keep alive time - var minimalKeepAliveTime = Math.max(config.getMinKeepAliveTime(), packet.getKeepAlive()); - var keepAlive = config.isKeepAliveEnabled() ? minimalKeepAliveTime : SERVER_KEEP_ALIVE_DISABLED; - - // select result session expiry interval - var sessionExpiryInterval = config.isSessionsEnabled() ? - packet.getSessionExpiryInterval() : SESSION_EXPIRY_INTERVAL_DISABLED; - - if (sessionExpiryInterval == SESSION_EXPIRY_INTERVAL_UNDEFINED) { - sessionExpiryInterval = config.getDefaultSessionExpiryInterval(); - } - - // select result receive max - var receiveMax = packet.getReceiveMax() == RECEIVE_MAXIMUM_UNDEFINED ? - config.getReceiveMaximum() : Math.min(packet.getReceiveMax(), config.getReceiveMaximum()); - - // select result maximum packet size - var maximumPacketSize = packet.getMaximumPacketSize() == MAXIMUM_PACKET_SIZE_UNDEFINED ? - config.getMaximumPacketSize() : Math.min(packet.getMaximumPacketSize(), config.getMaximumPacketSize()); - - // select result topic alias maximum - var topicAliasMaximum = packet.getTopicAliasMaximum() == TOPIC_ALIAS_MAXIMUM_UNDEFINED ? - TOPIC_ALIAS_MAXIMUM_DISABLED : Math.min(packet.getTopicAliasMaximum(), config.getTopicAliasMaximum()); - - client.setSession(session); - client.configure( - sessionExpiryInterval, - receiveMax, - maximumPacketSize, - topicAliasMaximum, - keepAlive, - packet.isRequestResponseInformation(), - packet.isRequestProblemInformation() - ); - - var connectAck = client.getPacketOutFactory().newConnectAck( - client, - ConnectAckReasonCode.SUCCESS, - sessionRestored, - packet.getClientId(), - packet.getSessionExpiryInterval(), - packet.getKeepAlive(), - packet.getReceiveMax() - ); - - subscriptionService.restoreSubscriptions(client, session); - - return Mono.fromFuture(client.sendWithFeedback(connectAck) - .thenApply(result -> onSentConnAck(client, session, result))); - } - - private boolean onSentConnAck(UnsafeMqttClient client, MqttSession session, boolean result) { - - if (!result) { - log.warn("Was issue with sending conn ack packet to client {}", client.getClientId()); - return false; - } - - session.resendPendingPackets(client); - return true; - } - - private boolean checkPacketException(UnsafeMqttClient client, ConnectInPacket packet) { - - var exception = packet.getException(); - - if (exception instanceof ConnectionRejectException) { - client.reject(((ConnectionRejectException) exception).getReasonCode()); - return true; - } else if (exception instanceof MalformedPacketMqttException) { - client.reject(ConnectAckReasonCode.MALFORMED_PACKET); - return true; - } - - return false; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/DisconnetInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/DisconnetInPacketHandler.java deleted file mode 100644 index b09a9de2..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/DisconnetInPacketHandler.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.DisconnectInPacket; -import lombok.extern.log4j.Log4j2; - -@Log4j2 -public class DisconnetInPacketHandler extends AbstractPacketHandler { - - @Override - protected void handleImpl(UnsafeMqttClient client, DisconnectInPacket packet) { - - var reasonCode = packet.getReasonCode(); - - if (reasonCode == DisconnectReasonCode.NORMAL_DISCONNECTION) { - log.info("Disconnect client {}", client); - } else { - log.error("Disconnect client {} by error reason {}", client, reasonCode); - } - - client.getConnection().close(); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PacketInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/PacketInHandler.java deleted file mode 100644 index 15d9b624..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PacketInHandler.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; - -public interface PacketInHandler { - - PacketInHandler EMPTY = (client, packet) -> {}; - - void handle(MqttClient.UnsafeMqttClient client, MqttReadablePacket packet); -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PendingOutResponseInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/PendingOutResponseInPacketHandler.java deleted file mode 100644 index b92096f6..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PendingOutResponseInPacketHandler.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class PendingOutResponseInPacketHandler extends - AbstractPacketHandler { - - @Override - protected void handleImpl(UnsafeMqttClient client, R packet) { - var session = client.getSession(); - if (session != null) { - session.updateOutPendingPacket(client, packet); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishInPacketHandler.java deleted file mode 100644 index c55ca23f..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishInPacketHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.service.PublishingService; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class PublishInPacketHandler extends AbstractPacketHandler { - - private final PublishingService publishingService; - - @Override - protected void handleImpl(UnsafeMqttClient client, PublishInPacket packet) { - publishingService.publish(client, packet); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReleaseInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReleaseInPacketHandler.java deleted file mode 100644 index 39a89323..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/PublishReleaseInPacketHandler.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class PublishReleaseInPacketHandler extends AbstractPacketHandler { - - @Override - protected void handleImpl(UnsafeMqttClient client, PublishReleaseInPacket packet) { - var session = client.getSession(); - if (session != null) { - session.updateInPendingPacket(client, packet); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/UnsubscribeInPacketHandler.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/UnsubscribeInPacketHandler.java deleted file mode 100644 index 2156beaf..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/UnsubscribeInPacketHandler.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.ss.mqtt.broker.handler.packet.in; - -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.in.UnsubscribeInPacket; -import com.ss.mqtt.broker.service.SubscriptionService; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class UnsubscribeInPacketHandler extends AbstractPacketHandler { - - private final SubscriptionService subscriptionService; - - @Override - protected void handleImpl(UnsafeMqttClient client, UnsubscribeInPacket packet) { - var ackReasonCodes = subscriptionService.unsubscribe(client, packet.getTopicFilters()); - client.send(client.getPacketOutFactory().newUnsubscribeAck(packet.getPacketId(), ackReasonCodes)); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/packet/in/package-info.java b/src/main/java/com/ss/mqtt/broker/handler/packet/in/package-info.java deleted file mode 100644 index 9bbe9ac0..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/packet/in/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.handler.packet.in; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/AbstractPublishInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/AbstractPublishInHandler.java deleted file mode 100644 index 16b9b79f..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/AbstractPublishInHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.in; - -import com.ss.mqtt.broker.handler.publish.out.PublishOutHandler; -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.SingleSubscriber; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.service.SubscriptionService; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -abstract class AbstractPublishInHandler implements PublishInHandler { - - protected final SubscriptionService subscriptionService; - protected final PublishOutHandler[] publishOutHandlers; - - public void handle(MqttClient client, PublishInPacket packet) { - handleResult(client, packet, subscriptionService.forEachTopicSubscriber( - packet.getTopicName(), - packet, - this::sendToSubscriber - )); - } - - private ActionResult sendToSubscriber( - SingleSubscriber subscriber, - PublishInPacket packet - ) { - return publishOutHandler(subscriber.getQos()).handle(packet, subscriber); - } - - private PublishOutHandler publishOutHandler(QoS qos) { - return publishOutHandlers[qos.ordinal()]; - } - - protected void handleResult( - MqttClient client, - PublishInPacket packet, - ActionResult result - ) { - // nothing to do - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/PublishInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/PublishInHandler.java deleted file mode 100644 index f2f2cf09..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/PublishInHandler.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.in; - -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; - -/** - * Interface to handle incoming publish packets. - */ -public interface PublishInHandler { - - void handle(MqttClient client, PublishInPacket packet); -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos0PublishInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos0PublishInHandler.java deleted file mode 100644 index 4668dc19..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos0PublishInHandler.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.in; - -import com.ss.mqtt.broker.handler.publish.out.PublishOutHandler; -import com.ss.mqtt.broker.service.SubscriptionService; - -public class Qos0PublishInHandler extends AbstractPublishInHandler { - - public Qos0PublishInHandler( - SubscriptionService subscriptionService, - PublishOutHandler[] publishOutHandlers - ) { - super(subscriptionService, publishOutHandlers); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos1PublishInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos1PublishInHandler.java deleted file mode 100644 index aecf7192..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos1PublishInHandler.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.in; - -import com.ss.mqtt.broker.handler.publish.out.PublishOutHandler; -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.service.SubscriptionService; - -public class Qos1PublishInHandler extends AbstractPublishInHandler { - - public Qos1PublishInHandler( - SubscriptionService subscriptionService, - PublishOutHandler[] publishOutHandlers - ) { - super(subscriptionService, publishOutHandlers); - } - - @Override - public void handle(MqttClient client, PublishInPacket packet) { - - var session = client.getSession(); - - // it means this client was already closed - if (session == null) { - return; - } - - super.handle(client, packet); - } - - @Override - protected void handleResult( - MqttClient client, - PublishInPacket packet, - ActionResult result - ) { - - PublishAckReasonCode reasonCode; - - switch (result) { - case EMPTY: - reasonCode = PublishAckReasonCode.NO_MATCHING_SUBSCRIBERS; - break; - case SUCCESS: - reasonCode = PublishAckReasonCode.SUCCESS; - break; - default: - reasonCode = PublishAckReasonCode.UNSPECIFIED_ERROR; - break; - } - - client.send(client.getPacketOutFactory().newPublishAck( - packet.getPacketId(), - reasonCode - )); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos2PublishInHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos2PublishInHandler.java deleted file mode 100644 index 774f4cea..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/Qos2PublishInHandler.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.in; - -import com.ss.mqtt.broker.handler.publish.out.PublishOutHandler; -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket; -import com.ss.mqtt.broker.service.SubscriptionService; - -public class Qos2PublishInHandler extends AbstractPublishInHandler implements MqttSession.PendingPacketHandler { - - public Qos2PublishInHandler( - SubscriptionService subscriptionService, - PublishOutHandler[] publishOutHandlers - ) { - super(subscriptionService, publishOutHandlers); - } - - @Override - public void handle(MqttClient client, PublishInPacket packet) { - - var session = client.getSession(); - - // it means this client was already closed - if (session == null) { - return; - } - - // if this packet is re-try from client - if (packet.isDuplicate()) { - // if this packet was accepted before then we can skip it - if (session.hasInPending(packet.getPacketId())) { - return; - } - } - - super.handle(client, packet); - } - - @Override - protected void handleResult( - MqttClient client, - PublishInPacket packet, - ActionResult result - ) { - - // because it was checked - final MqttSession session = client.getSession(); - - // it means this client was already closed - if (session == null) { - return; - } - - PublishReceivedReasonCode reasonCode; - - switch (result) { - case EMPTY: - reasonCode = PublishReceivedReasonCode.NO_MATCHING_SUBSCRIBERS; - break; - case SUCCESS: - reasonCode = PublishReceivedReasonCode.SUCCESS; - break; - default: - reasonCode = PublishReceivedReasonCode.UNSPECIFIED_ERROR; - break; - } - - session.registerInPublish(packet, this, packet.getPacketId()); - - client.send(client.getPacketOutFactory().newPublishReceived( - packet.getPacketId(), - reasonCode - )); - } - - @Override - public boolean handleResponse(MqttClient client, HasPacketId response) { - - if (!(response instanceof PublishReleaseInPacket)) { - throw new IllegalStateException("Unexpected response " + response); - } - - var packetOutFactory = client.getPacketOutFactory(); - client.send(packetOutFactory.newPublishCompleted( - response.getPacketId(), - PublishCompletedReasonCode.SUCCESS - )); - - return true; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/in/package-info.java b/src/main/java/com/ss/mqtt/broker/handler/publish/in/package-info.java deleted file mode 100644 index d40a6868..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/in/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.handler.publish.in; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/AbstractPublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/AbstractPublishOutHandler.java deleted file mode 100644 index 6803243d..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/AbstractPublishOutHandler.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import com.ss.mqtt.broker.model.*; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; - -abstract class AbstractPublishOutHandler implements PublishOutHandler { - - @Override - public ActionResult handle(PublishInPacket packet, SingleSubscriber subscriber) { - - var client = subscriber.getMqttClient(); - var session = client.getSession(); - - // if session is null it means this client was already closed - if (session != null) { - return handleImpl(packet, subscriber, client, session); - } else { - return ActionResult.EMPTY; - } - } - - protected abstract ActionResult handleImpl( - PublishInPacket packet, - Subscriber subscriber, - MqttClient client, - MqttSession session - ); - - protected abstract QoS getQoS(); - - void sendPublish( - MqttClient client, - PublishInPacket packet, - int packetId, - boolean duplicate - ) { - - var packetOutFactory = client.getPacketOutFactory(); - client.send(packetOutFactory.newPublish( - packetId, - getQoS(), - packet.isRetained(), - duplicate, - packet.getTopicName().toString(), - MqttPropertyConstants.TOPIC_ALIAS_NOT_SET, - packet.getPayload(), - packet.isPayloadFormatIndicator(), - packet.getResponseTopic(), - packet.getCorrelationData(), - packet.getUserProperties() - )); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/PersistentPublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/PersistentPublishOutHandler.java deleted file mode 100644 index 9cf70332..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/PersistentPublishOutHandler.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.Subscriber; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; - -public abstract class PersistentPublishOutHandler extends AbstractPublishOutHandler implements - MqttSession.PendingPacketHandler { - - @Override - protected ActionResult handleImpl( - PublishInPacket packet, - Subscriber subscriber, - MqttClient client, - MqttSession session - ) { - // generate new uniq packet id per client - var packetId = session.nextPacketId(); - - // register waiting async response - session.registerOutPublish(packet, this, packetId); - - // send publish - sendPublish(client, packet, packetId, false); - - return ActionResult.SUCCESS; - } - - @Override - public void resend(MqttClient client, PublishInPacket packet, int packetId) { - sendPublish(client, packet, packetId, true); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/PublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/PublishOutHandler.java deleted file mode 100644 index ca87a263..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/PublishOutHandler.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.SingleSubscriber; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; - -/** - * Interface to handle outgoing publish packets. - */ -public interface PublishOutHandler { - - ActionResult handle(PublishInPacket packet, SingleSubscriber subscriber); -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos0PublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos0PublishOutHandler.java deleted file mode 100644 index 29982c39..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos0PublishOutHandler.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import static com.ss.mqtt.broker.model.ActionResult.SUCCESS; -import com.ss.mqtt.broker.model.ActionResult; -import com.ss.mqtt.broker.model.*; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; - -public class Qos0PublishOutHandler extends AbstractPublishOutHandler { - - @Override - protected QoS getQoS() { - return QoS.AT_MOST_ONCE; - } - - @Override - protected ActionResult handleImpl( - PublishInPacket packet, - Subscriber subscriber, - MqttClient client, - MqttSession session - ) { - sendPublish(client, packet, 0, false); - return SUCCESS; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos1PublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos1PublishOutHandler.java deleted file mode 100644 index cd60951f..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos1PublishOutHandler.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class Qos1PublishOutHandler extends PersistentPublishOutHandler { - - @Override - protected QoS getQoS() { - return QoS.AT_LEAST_ONCE; - } - - @Override - public boolean handleResponse(MqttClient client, HasPacketId response) { - - if (!(response instanceof PublishAckInPacket)) { - throw new IllegalStateException("Unexpected response: " + response); - } - - // just return 'true' to remove pending packet from session - return true; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos2PublishOutHandler.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos2PublishOutHandler.java deleted file mode 100644 index b9d0064f..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/Qos2PublishOutHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.ss.mqtt.broker.handler.publish.out; - -import static com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode.SUCCESS; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket; -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public final class Qos2PublishOutHandler extends PersistentPublishOutHandler { - - @Override - protected QoS getQoS() { - return QoS.EXACTLY_ONCE; - } - - @Override - public boolean handleResponse(MqttClient client, HasPacketId response) { - - var packetOutFactory = client.getPacketOutFactory(); - - if (response instanceof PublishReceivedInPacket) { - client.send(packetOutFactory.newPublishRelease(response.getPacketId(), SUCCESS)); - return false; - } else if (response instanceof PublishCompleteInPacket) { - return true; - } else { - throw new IllegalStateException("Unexpected response: " + response); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/handler/publish/out/package-info.java b/src/main/java/com/ss/mqtt/broker/handler/publish/out/package-info.java deleted file mode 100644 index 7e85dafc..00000000 --- a/src/main/java/com/ss/mqtt/broker/handler/publish/out/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.handler.publish.out; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/model/ActionResult.java b/src/main/java/com/ss/mqtt/broker/model/ActionResult.java deleted file mode 100644 index dfa638c0..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/ActionResult.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ss.mqtt.broker.model; - -import org.jetbrains.annotations.NotNull; - -public enum ActionResult { - - SUCCESS, - FAILED, - EMPTY; - - public @NotNull ActionResult and(@NotNull ActionResult another) { - if (this == FAILED || another == FAILED) { - return FAILED; - } else if (this == SUCCESS || another == SUCCESS) { - return SUCCESS; - } else { - return EMPTY; - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/MqttPropertyConstants.java b/src/main/java/com/ss/mqtt/broker/model/MqttPropertyConstants.java deleted file mode 100644 index bddc8315..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/MqttPropertyConstants.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.ss.mqtt.broker.model; - -public interface MqttPropertyConstants { - - QoS MAXIMUM_QOS_DEFAULT = QoS.EXACTLY_ONCE; - - int MAXIMUM_PROTOCOL_PACKET_SIZE = 256 * 1024 * 1024; - int MAXIMUM_PACKET_ID = 0xFFFF; - - long SESSION_EXPIRY_INTERVAL_DISABLED = 0; - long SESSION_EXPIRY_INTERVAL_DEFAULT = 120; - long SESSION_EXPIRY_INTERVAL_MIN = 0; - long SESSION_EXPIRY_INTERVAL_INFINITY = 0xFFFFFFFFL; - long SESSION_EXPIRY_INTERVAL_UNDEFINED = -1; - - int RECEIVE_MAXIMUM_UNDEFINED = -1; - int RECEIVE_MAXIMUM_MIN = 1; - int RECEIVE_MAXIMUM_DEFAULT = 10; - int RECEIVE_MAXIMUM_MAX = 0xFFFF; - - int MAXIMUM_PACKET_SIZE_UNDEFINED = -1; - int MAXIMUM_PACKET_SIZE_DEFAULT = 1024; - int MAXIMUM_PACKET_SIZE_MIN = 1; - int MAXIMUM_PACKET_SIZE_MAX = MAXIMUM_PROTOCOL_PACKET_SIZE; - - boolean PAYLOAD_FORMAT_INDICATOR_DEFAULT = false; - - long MESSAGE_EXPIRY_INTERVAL_UNDEFINED = -1; - long MESSAGE_EXPIRY_INTERVAL_INFINITY = 0; - - int TOPIC_ALIAS_MAXIMUM_UNDEFINED = -1; - int TOPIC_ALIAS_MAXIMUM_DISABLED = 0; - - int SERVER_KEEP_ALIVE_UNDEFINED = -1; - int SERVER_KEEP_ALIVE_DISABLED = 0; - int SERVER_KEEP_ALIVE_DEFAULT = 0; - int SERVER_KEEP_ALIVE_MIN = 0; - int SERVER_KEEP_ALIVE_MAX = 0xFFFF; - - int TOPIC_ALIAS_DEFAULT = 0; - int TOPIC_ALIAS_MIN = 0; - int TOPIC_ALIAS_MAX = 0xFFFF; - int TOPIC_ALIAS_NOT_SET = 0; - - int SUBSCRIPTION_ID_UNDEFINED = 0; - - boolean SESSIONS_ENABLED_DEFAULT = true; - boolean KEEP_ALIVE_ENABLED_DEFAULT = false; - boolean RETAIN_AVAILABLE_DEFAULT = false; - boolean WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT = false; - boolean SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT = false; - boolean SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT = false; - - int PACKET_ID_FOR_QOS_0 = 0; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/MqttSession.java b/src/main/java/com/ss/mqtt/broker/model/MqttSession.java deleted file mode 100644 index 5499555a..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/MqttSession.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import javasabr.rlib.functions.TriConsumer; -import org.jetbrains.annotations.NotNull; - -public interface MqttSession { - - interface UnsafeMqttSession extends MqttSession { - - void setExpirationTime(long expirationTime); - - void clear(); - - void onPersisted(); - - void onRestored(); - } - - interface PendingPacketHandler { - - /** - * @return true if pending packet can be removed. - */ - boolean handleResponse(@NotNull MqttClient client, @NotNull HasPacketId response); - - default void resend(@NotNull MqttClient client, @NotNull PublishInPacket packet, int packetId) {} - } - - @NotNull String getClientId(); - - int nextPacketId(); - - /** - * @return the expiration time in ms or -1 if it should not be expired now. - */ - long getExpirationTime(); - - void resendPendingPackets(@NotNull MqttClient client); - - boolean hasOutPending(); - boolean hasInPending(); - - boolean hasInPending(int packetId); - boolean hasOutPending(int packetId); - - void registerOutPublish(@NotNull PublishInPacket publish, @NotNull PendingPacketHandler handler, int packetId); - void registerInPublish(@NotNull PublishInPacket publish, @NotNull PendingPacketHandler handler, int packetId); - - void updateOutPendingPacket(@NotNull MqttClient client, @NotNull HasPacketId response); - void updateInPendingPacket(@NotNull MqttClient client, @NotNull HasPacketId response); - - void forEachTopicFilter( - @NotNull F first, - @NotNull S second, - @NotNull TriConsumer consumer - ); - void addSubscriber(@NotNull SubscribeTopicFilter subscribe); - void removeSubscriber(@NotNull TopicFilter subscribe); -} diff --git a/src/main/java/com/ss/mqtt/broker/model/MqttVersion.java b/src/main/java/com/ss/mqtt/broker/model/MqttVersion.java deleted file mode 100644 index 425cc5a3..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/MqttVersion.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.factory.packet.out.Mqtt311PacketOutFactory; -import com.ss.mqtt.broker.factory.packet.out.Mqtt5PacketOutFactory; -import com.ss.mqtt.broker.factory.packet.out.MqttPacketOutFactory; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; - -@Getter -public enum MqttVersion { - UNKNOWN("Unknown", -1, new Mqtt311PacketOutFactory()), - MQTT_3_1_1("MQTT", 4, new Mqtt311PacketOutFactory()), - MQTT_5("MQTT", 5, new Mqtt5PacketOutFactory()); - - private static final Map NAME_LEVEL_VERSIONS; - - static { - - var map = new HashMap(); - - for (var mqttVersion : values()) { - - if (mqttVersion.version < 0) { - continue; - } - - var versions = map.computeIfAbsent(mqttVersion.name, name -> new MqttVersion[mqttVersion.version + 1]); - - if (versions.length > mqttVersion.version) { - versions[mqttVersion.version] = mqttVersion; - continue; - } - - versions = Arrays.copyOf(versions, mqttVersion.version + 1); - versions[mqttVersion.version] = mqttVersion; - - map.replace(mqttVersion.name, versions); - } - - NAME_LEVEL_VERSIONS = Map.copyOf(map); - } - - public static @NotNull MqttVersion of(@NotNull String name, byte level) { - - if (level < 0) { - return MqttVersion.UNKNOWN; - } - - var availableVersions = NAME_LEVEL_VERSIONS.get(name); - - if (availableVersions == null) { - return MqttVersion.UNKNOWN; - } else if (availableVersions.length <= level || availableVersions[level] == null) { - return MqttVersion.UNKNOWN; - } - - return availableVersions[level]; - } - - private final @NotNull MqttPacketOutFactory packetOutFactory; - private final byte @NotNull [] nameInBytes; - private final @NotNull String name; - private final byte version; - - MqttVersion(@NotNull String name, int version, @NotNull MqttPacketOutFactory packetOutFactory) { - this.name = name; - this.version = (byte) version; - this.nameInBytes = name.getBytes(StandardCharsets.UTF_8); - this.packetOutFactory = packetOutFactory; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/PacketProperty.java b/src/main/java/com/ss/mqtt/broker/model/PacketProperty.java deleted file mode 100644 index fb306c10..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/PacketProperty.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.model.data.type.PacketDataType; -import javasabr.rlib.common.util.ClassUtils; -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.stream.Stream; - -public enum PacketProperty { - PAYLOAD_FORMAT_INDICATOR(0x01, PacketDataType.BYTE), - MESSAGE_EXPIRY_INTERVAL(0x02, PacketDataType.INTEGER), - CONTENT_TYPE(0x03, PacketDataType.UTF_8_STRING), - RESPONSE_TOPIC(0x08, PacketDataType.UTF_8_STRING), - CORRELATION_DATA(0x09, PacketDataType.BINARY), - SUBSCRIPTION_IDENTIFIER(0x0B, PacketDataType.MULTI_BYTE_INTEGER), - SESSION_EXPIRY_INTERVAL(0x11, PacketDataType.INTEGER), - ASSIGNED_CLIENT_IDENTIFIER(0x12, PacketDataType.UTF_8_STRING), - SERVER_KEEP_ALIVE(0x13, PacketDataType.SHORT), - AUTHENTICATION_METHOD(0x15, PacketDataType.UTF_8_STRING), - AUTHENTICATION_DATA(0x16, PacketDataType.BINARY), - REQUEST_PROBLEM_INFORMATION(0x17, PacketDataType.BYTE), - WILL_DELAY_INTERVAL(0x18, PacketDataType.INTEGER), - REQUEST_RESPONSE_INFORMATION(0x19, PacketDataType.BYTE), - RESPONSE_INFORMATION(0x1A, PacketDataType.UTF_8_STRING), - SERVER_REFERENCE(0x1C, PacketDataType.UTF_8_STRING), - REASON_STRING(0x1F, PacketDataType.UTF_8_STRING), - RECEIVE_MAXIMUM(0x21, PacketDataType.SHORT), - TOPIC_ALIAS_MAXIMUM(0x22, PacketDataType.SHORT), - TOPIC_ALIAS(0x23, PacketDataType.SHORT), - MAXIMUM_QOS(0x24, PacketDataType.BYTE), - RETAIN_AVAILABLE(0x25, PacketDataType.BYTE), - USER_PROPERTY(0x26, PacketDataType.UTF_8_STRING_PAIR), - MAXIMUM_PACKET_SIZE(0x27, PacketDataType.INTEGER), - WILDCARD_SUBSCRIPTION_AVAILABLE(0x28, PacketDataType.BYTE), - SUBSCRIPTION_IDENTIFIER_AVAILABLE(0x29, PacketDataType.BYTE), - SHARED_SUBSCRIPTION_AVAILABLE(0x2A, PacketDataType.BYTE); - - private static final PacketProperty[] PROPERTIES; - - static { - - int maxId = Stream.of(values()) - .mapToInt(PacketProperty::getId) - .max() - .orElse(0); - - var result = new PacketProperty[maxId + 1]; - - Stream.of(values()) - .forEach(prop -> result[prop.id] = prop); - - PROPERTIES = result; - } - - public static @NotNull PacketProperty of(int id) { - if (id < 0 || id >= PROPERTIES.length) { - throw new IllegalArgumentException("Unknown property with id: " + id); - } else { - return PROPERTIES[id]; - } - } - - private final @Getter byte id; - private final @Getter @NotNull PacketDataType dataType; - private final @Nullable Object defaultValue; - - PacketProperty(int id, @NotNull PacketDataType dataType) { - this(id, dataType, null); - } - - PacketProperty(int id, @NotNull PacketDataType dataType, @Nullable Object defaultValue) { - this.id = (byte) id; - this.dataType = dataType; - this.defaultValue = defaultValue; - } - - public @NotNull T getDefaultValue() { - return ClassUtils.unsafeNNCast(ObjectUtils.notNull(defaultValue)); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/QoS.java b/src/main/java/com/ss/mqtt/broker/model/QoS.java deleted file mode 100644 index db426abc..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/QoS.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@Getter -@RequiredArgsConstructor -public enum QoS { - AT_MOST_ONCE(SubscribeAckReasonCode.GRANTED_QOS_0), - AT_LEAST_ONCE(SubscribeAckReasonCode.GRANTED_QOS_1), - EXACTLY_ONCE(SubscribeAckReasonCode.GRANTED_QOS_2), - INVALID(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR); - - private static final QoS[] VALUES = values(); - - public static @NotNull QoS of(int level) { - if (level < 0 || level > EXACTLY_ONCE.ordinal()) { - return INVALID; - } else { - return VALUES[level]; - } - } - - private final SubscribeAckReasonCode subscribeAckReasonCode; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/SingleSubscriber.java b/src/main/java/com/ss/mqtt/broker/model/SingleSubscriber.java deleted file mode 100644 index 37b40986..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/SingleSubscriber.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ss.mqtt.broker.model; - -import com.ss.mqtt.broker.network.client.MqttClient; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import lombok.ToString; -import org.jetbrains.annotations.NotNull; - -@ToString -@EqualsAndHashCode(of = "mqttClient") -@RequiredArgsConstructor -public final class SingleSubscriber implements Subscriber { - - private final @Getter @NotNull MqttClient mqttClient; - private final @NotNull SubscribeTopicFilter subscribe; - - public @NotNull QoS getQos() { - return subscribe.getQos(); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/SubscribeRetainHandling.java b/src/main/java/com/ss/mqtt/broker/model/SubscribeRetainHandling.java deleted file mode 100644 index 6f76a5e1..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/SubscribeRetainHandling.java +++ /dev/null @@ -1,29 +0,0 @@ -package com.ss.mqtt.broker.model; - -import org.jetbrains.annotations.NotNull; - -public enum SubscribeRetainHandling { - /** - * Send retained messages at the time of the subscribe. - */ - SEND, - /** - * Send retained messages at subscribe only if the subscription does not currently exist. - */ - SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST, - /** - * Do not send retained messages at the time of the subscribe. - */ - DO_NOT_SEND, - INVALID; - - private static final SubscribeRetainHandling[] VALUES = values(); - - public static @NotNull SubscribeRetainHandling of(int level) { - if (level < 0 || level > DO_NOT_SEND.ordinal()) { - return SubscribeRetainHandling.INVALID; - } else { - return VALUES[level]; - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/SubscribeTopicFilter.java b/src/main/java/com/ss/mqtt/broker/model/SubscribeTopicFilter.java deleted file mode 100644 index b56305e8..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/SubscribeTopicFilter.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ss.mqtt.broker.model; - -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@Getter -@EqualsAndHashCode -@RequiredArgsConstructor -public class SubscribeTopicFilter { - - /** - * The subscriber's topic filter. - */ - private final @NotNull TopicFilter topicFilter; - - /** - * Maximum QoS field. This gives the maximum QoS level at which the Server - * can send Application Messages to the Client. - */ - private final @NotNull QoS qos; - - /** - * This option specifies whether retained messages are sent when the subscription is established. - * This does not affect the sending of retained messages at any point after the subscribe. - * If there are no retained messages matching the Topic Filter, all of these values act the same. - */ - private final @NotNull SubscribeRetainHandling retainHandling; - - /** - * If the value is true, Application Messages MUST NOT be forwarded to a connection with a ClientID equal - * to the ClientID of the publishing connection. - */ - private final boolean noLocal; - - /** - * If true, Application Messages forwarded using this subscription keep the RETAIN flag they were published with. - * If false, Application Messages forwarded using this subscription have the RETAIN flag set to 0. - * Retained messages sent when the subscription is established have the RETAIN flag set to 1. - */ - private final boolean retainAsPublished; - - public SubscribeTopicFilter(@NotNull String topicFilter, @NotNull QoS qos) { - this(buildTopicFilter(topicFilter), qos, SubscribeRetainHandling.SEND, true, true); - } - - public SubscribeTopicFilter(@NotNull TopicFilter topicFilter, @NotNull QoS qos) { - this(topicFilter, qos, SubscribeRetainHandling.SEND, true, true); - } - - @Override - public @NotNull String toString() { - return "SubscribeTopicFilter(" + "topicFilter=" + topicFilter.getRawTopic() + ", qos=" + qos + ", retainHandling=" + - retainHandling + ", noLocal=" + noLocal + ", retainAsPublished=" + retainAsPublished + ')'; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/model/data/type/PacketDataType.java b/src/main/java/com/ss/mqtt/broker/model/data/type/PacketDataType.java deleted file mode 100644 index 63848fd5..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/data/type/PacketDataType.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ss.mqtt.broker.model.data.type; - -public enum PacketDataType { - BYTE, - SHORT, - INTEGER, - MULTI_BYTE_INTEGER, - BINARY, - UTF_8_STRING, - UTF_8_STRING_PAIR -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/AuthenticateReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/AuthenticateReasonCode.java deleted file mode 100644 index 26a382bb..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/AuthenticateReasonCode.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum AuthenticateReasonCode { - - /** - * Authentication is successful. - * Server. - */ - SUCCESS((byte) 0x00), - /** - * Continue the authentication with another step. - * Client or Server. - */ - CONTINUE_AUTHENTICATION((byte) 0x18), - /** - * Initiate a re-authentication. - * Client. - */ - RE_AUTHENTICATE((byte) 0x19); - - private static final AuthenticateReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(AuthenticateReasonCode::getValue) - .max() - .orElse(0); - - var values = new AuthenticateReasonCode[maxId + 1]; - - for (var value : values()) { - values[value.value] = value; - } - - VALUES = values; - } - - public static @NotNull AuthenticateReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/ConnectAckReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/ConnectAckReasonCode.java deleted file mode 100644 index ff455212..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/ConnectAckReasonCode.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum ConnectAckReasonCode { - /** - * The Connection is accepted. - */ - SUCCESS((byte) 0x00, (byte) 0x00), - - // WITH REASONS BELOW SERVER MUST CLOSE CONNECTION - - /** - * The Server does not wish to reveal the reason for the - * failure, or none of the other Reason Codes apply. - */ - UNSPECIFIED_ERROR((byte) 0x01, (byte) 0x80), - /** - * Data within the CONNECT packet could not be - * correctly parsed. - */ - MALFORMED_PACKET((byte) 0x01, (byte) 0x81), - /** - * Data in the CONNECT packet does not conform to this - * specification. - */ - PROTOCOL_ERROR((byte) 0x01, (byte) 0x82), - /** - * The CONNECT is valid but is not accepted by this - * Server. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x01, (byte) 0x83), - /** - * The Server does not support the version of the MQTT - * protocol requested by the Client - */ - UNSUPPORTED_PROTOCOL_VERSION((byte) 0x01, (byte) 0x84), - /** - * The Client Identifier is a valid string but is not allowed - * by the Server. - */ - CLIENT_IDENTIFIER_NOT_VALID((byte) 0x02, (byte) 0x85), - /** - * The Server does not accept the User Name or - * Password specified by the Client - */ - BAD_USER_NAME_OR_PASSWORD((byte) 0x04, (byte) 0x86), - /** - * The Client is not authorized to connect. - */ - NOT_AUTHORIZED((byte) 0x05, (byte) 0x87), - /** - * The MQTT Server is not available - */ - SERVER_UNAVAILABLE((byte) 0x03, (byte) 0x88), - /** - * The Server is busy. Try again later. - */ - SERVER_BUSY((byte) 0x01, (byte) 0x89), - /** - * This Client has been banned by administrative action. - * Contact the server administrator. - */ - BANNED((byte) 0x01, (byte) 0x8A), - /** - * The authentication method is not supported or does not - * match the authentication method currently in use. - */ - BAD_AUTHENTICATION_METHOD((byte) 0x04, (byte) 0x8C), - /** - * The Will Topic Name is not malformed, but is not - * accepted by this Server. - */ - TOPIC_NAME_INVALID((byte) 0x01, (byte) 0x90), - /** - * The CONNECT packet exceeded the maximum - * permissible size. - */ - PACKET_TOO_LARGE((byte) 0x01, (byte) 0x95), - /** - * An implementation or administrative imposed limit has - * been exceeded. - */ - QUOTA_EXCEEDED((byte) 0x01, (byte) 0x97), - /** - * The Will Payload does not match the specified Payload - * Format Indicator. - */ - PAYLOAD_FORMAT_INVALID((byte) 0x01, (byte) 0x99), - /** - * The Server does not support retained messages, and - * Will Retain was set to 1. - */ - RETAIN_NOT_SUPPORTED((byte) 0x01, (byte) 0x9A), - /** - * The Server does not support the QoS set in Will QoS. - */ - QOS_NOT_SUPPORTED((byte) 0x01, (byte) 0x9B), - /** - * The Client should temporarily use another server. - */ - USE_ANOTHER_SERVER((byte) 0x01, (byte) 0x9C), - /** - * The Client should permanently use another server. - */ - SERVER_MOVED((byte) 0x01, (byte) 0x9D), - /** - * The connection rate limit has been exceeded. - */ - CONNECTION_RATE_EXCEEDED((byte) 0x01, (byte) 0x9F); - - private static final ConnectAckReasonCode[] MQTT_5_VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(ConnectAckReasonCode::getMqtt5) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new ConnectAckReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.mqtt5)] = value; - } - - MQTT_5_VALUES = values; - } - - public static @NotNull ConnectAckReasonCode of(boolean mqtt5, int reasonCode) { - return mqtt5 ? ofMqtt5(reasonCode) : ofMqtt311(reasonCode); - } - - public static @NotNull ConnectAckReasonCode ofMqtt311(int reasonCode) { - switch (reasonCode) { - case 0x00: - return SUCCESS; - case 0x01: - return UNSUPPORTED_PROTOCOL_VERSION; - case 0x02: - return CLIENT_IDENTIFIER_NOT_VALID; - case 0x03: - return SERVER_UNAVAILABLE; - case 0x04: - return BAD_USER_NAME_OR_PASSWORD; - case 0x05: - return NOT_AUTHORIZED; - default: - throw new IllegalArgumentException("Unsupported reason code: " + reasonCode); - } - } - - public static @NotNull ConnectAckReasonCode ofMqtt5(int reasonCode) { - return ObjectUtils.notNull( - MQTT_5_VALUES[reasonCode], - reasonCode, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte mqtt311; - private @Getter final byte mqtt5; - -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/DisconnectReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/DisconnectReasonCode.java deleted file mode 100644 index d20aa9eb..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/DisconnectReasonCode.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum DisconnectReasonCode { - /** - * Close the connection normally. Do not send the Will Message. - * Client or Server. - */ - NORMAL_DISCONNECTION((byte) 0x00), - /** - * The Client wishes to disconnect but requires that the Server also publishes its Will Message. - * Client. - */ - DISCONNECT_WITH_WILL_MESSAGE((byte) 0x04), - - // ERRORS - - /** - * The Connection is closed but the sender either does not wish to reveal the reason, or none of - * the other Reason Codes apply. - * Client or Server. - */ - UNSPECIFIED_ERROR((byte) 0x80), - /** - * The received packet does not conform to this specification. - * Client or Server. - */ - MALFORMED_PACKET((byte) 0x81), - /** - * An unexpected or out of order packet was received. - * Client or Server. - */ - PROTOCOL_ERROR((byte) 0x82), - /** - * The packet received is valid but cannot be processed by this implementation. - * Client or Server. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), - /** - * The request is not authorized. - * Server. - */ - NOT_AUTHORIZED((byte) 0x87), - /** - * The Server is busy and cannot continue processing requests from this Client. - * Server. - */ - SERVER_BUSY((byte) 0x89), - /** - * The Server is shutting down. - * Server. - */ - SERVER_SHUTTING_DOWN((byte) 0x8B), - /** - * The Connection is closed because no packet has been received for 1.5 times the Keepalive time. - * Server. - */ - KEEP_ALIVE_TIMEOUT((byte) 0x8D), - /** - * Another Connection using the same ClientID has connected causing this Connection to be closed. - * Server. - */ - SESSION_TAKEN_OVER((byte) 0x8E), - /** - * The Topic Filter is correctly formed, but is not accepted by this Sever. - * Server. - */ - TOPIC_FILTER_INVALID((byte) 0x8F), - /** - * The Topic Name is correctly formed, but is not accepted by this Client or Server. - * Client or Server. - */ - TOPIC_NAME_INVALID((byte) 0x90), - /** - * The Client or Server has received more than Receive Maximum publication for which it has - * not sent PUBACK or PUBCOMP. - * Client or Server. - */ - RECEIVE_MAXIMUM_EXCEEDED((byte) 0x93), - /** - * The Client or Server has received a PUBLISH packet containing a Topic Alias which is greater - * than the Maximum Topic Alias it sent in the CONNECT or CONNACK packet. - * Client or Server. - */ - TOPIC_ALIAS_INVALID((byte) 0x94), - /** - * The packet size is greater than Maximum Packet Size for this Client or Server. - * Client or Server. - */ - PACKET_TOO_LARGE((byte) 0x95), - /** - * The received data rate is too high. - * Client or Server. - */ - MESSAGE_RATE_TOO_HIGH((byte) 0x96), - /** - * An implementation or administrative imposed limit has been exceeded. - * Client or Server. - */ - QUOTA_EXCEEDED((byte) 0x97), - /** - * The Connection is closed due to an administrative action. - * Client or Server. - */ - ADMINISTRATIVE_ACTION((byte) 0x98), - /** - * The payload format does not match the one specified by the Payload Format Indicator. - * Client or Server. - */ - PAYLOAD_FORMAT_INVALID((byte) 0x99), - /** - * The Server has does not support retained messages. - * Server. - */ - RETAIN_NOT_SUPPORTED((byte) 0x9A), - /** - * The Client specified a QoS greater than the QoS specified in a Maximum QoS in the CONNACK. - * Server. - */ - QOS_NOT_SUPPORTED((byte) 0x9B), - /** - * The Client should temporarily change its Server. - * Server. - */ - USE_ANOTHER_SERVER((byte) 0x9C), - /** - * The Server is moved and the Client should permanently change its server location. - * Server. - */ - SERVER_MOVED((byte) 0x9D), - /** - * The Server does not support Shared Subscriptions. - * Server. - */ - SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), - /** - * This connection is closed because the connection rate is too high. - * Server. - */ - CONNECTION_RATE_EXCEEDED((byte) 0x9F), - /** - * The maximum connection time authorized for this connection has been exceeded. - * Server. - */ - MAXIMUM_CONNECT_TIME((byte) 0xA0), - /** - * The Server does not support Subscription Identifiers; the subscription is not accepted. - * Server. - */ - SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), - /** - * The Server does not support Wildcard Subscriptions; the subscription is not accepted. - * Server. - */ - WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); - - private static final DisconnectReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(DisconnectReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new DisconnectReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull DisconnectReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishAckReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishAckReasonCode.java deleted file mode 100644 index bd87e4ed..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishAckReasonCode.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum PublishAckReasonCode { - /** - * The message is accepted. Publication of the QoS 1 - * message proceeds. - */ - SUCCESS((byte) 0x00), - /** - * The message is accepted but there are no - * subscribers. This is sent only by the Server. If the - * Server knows that there are no matching subscribers, - * it MAY use this Reason Code instead of 0x00 - * (Success). - */ - NO_MATCHING_SUBSCRIBERS((byte) 0x10), - - // ERRORS - - /** - * The receiver does not accept the publish but either - * does not want to reveal the reason, or it does not - * match one of the other values. - */ - UNSPECIFIED_ERROR((byte) 0x80), - /** - * The PUBLISH is valid but the receiver is not willing to - * accept it. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), - /** - * The PUBLISH is not authorized. - */ - NOT_AUTHORIZED((byte) 0x87), - /** - * The Topic Name is not malformed, but is not - * accepted by this Client or Server. - */ - TOPIC_NAME_INVALID((byte) 0x90), - /** - * The Packet Identifier is already in use. This might - * indicate a mismatch in the Session State between the - * Client and Server. - */ - PACKET_IDENTIFIER_IN_USE((byte) 0x91), - /** - * An implementation or administrative imposed limit has - * been exceeded. - */ - QUOTA_EXCEEDED((byte) 0x97), - /** - * The payload format does not match the specified - * Payload Format Indicator. - */ - PAYLOAD_FORMAT_INVALID((byte) 0x99); - - private static final PublishAckReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(PublishAckReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new PublishAckReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull PublishAckReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishCompletedReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishCompletedReasonCode.java deleted file mode 100644 index 56943b66..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishCompletedReasonCode.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum PublishCompletedReasonCode { - - /** - * Packet Identifier released. Publication of QoS 2 message is complete. - */ - SUCCESS((byte) 0x00), - /** - * The Packet Identifier is not known. This is not an error during recovery, - * but at other times indicates a mismatch between the Session State on the Client and Server. - */ - PACKET_IDENTIFIER_NOT_FOUND((byte) 0x92); - - private static final PublishCompletedReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(PublishCompletedReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new PublishCompletedReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull PublishCompletedReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReceivedReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReceivedReasonCode.java deleted file mode 100644 index af48a1b9..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReceivedReasonCode.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum PublishReceivedReasonCode { - - /** - * The message is accepted. Publication of the QoS 2 message proceeds.. - */ - SUCCESS((byte) 0x00), - /** - * The message is accepted but there are no subscribers. This is sent only by the Server. - * If the Server knows that there are no matching subscribers, it MAY use this Reason Code instead of 0x00 (Success). - */ - NO_MATCHING_SUBSCRIBERS((byte) 0x10), - /** - * The receiver does not accept the publish but either does not want to reveal the reason, or - * it does not match one of the other values. - */ - UNSPECIFIED_ERROR((byte) 0x80), - /** - * The PUBLISH is valid but the receiver is not willing to accept it. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), - /** - * The PUBLISH is not authorized. - */ - NOT_AUTHORIZED((byte) 0x87), - /** - * The Topic Name is not malformed, but is not accepted by this Client or Server. - */ - TOPIC_NAME_INVALID((byte) 0x90), - /** - * The Packet Identifier is already in use. This might indicate a mismatch in the Session State - * between the Client and Server. - */ - PACKET_IDENTIFIER_IN_USE((byte) 0x91), - /** - * An implementation or administrative imposed limit has been exceeded. - */ - QUOTA_EXCEEDED((byte) 0x97), - /** - * The payload format does not match the one specified in the Payload Format Indicator. - */ - PAYLOAD_FORMAT_INVALID((byte) 0x99); - - private static final PublishReceivedReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(PublishReceivedReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new PublishReceivedReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull PublishReceivedReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReleaseReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReleaseReasonCode.java deleted file mode 100644 index f00c2f41..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/PublishReleaseReasonCode.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum PublishReleaseReasonCode { - - /** - * Message released. - */ - SUCCESS((byte) 0x00), - /** - * The Packet Identifier is not known. This is not an error during recovery, but at other times indicates - * a mismatch between the Session State on the Client and Server. - */ - PACKET_IDENTIFIER_NOT_FOUND((byte) 0x92); - - private static final PublishReleaseReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(PublishReleaseReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new PublishReleaseReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull PublishReleaseReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/SubscribeAckReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/SubscribeAckReasonCode.java deleted file mode 100644 index 50db27aa..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/SubscribeAckReasonCode.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum SubscribeAckReasonCode { - /** - * The subscription is accepted and the maximum QoS sent will be - * QoS 0. This might be a lower QoS than was requested. - */ - GRANTED_QOS_0((byte) 0x00), - /** - * The subscription is accepted and the maximum QoS sent will be - * QoS 1. This might be a lower QoS than was requested. - */ - GRANTED_QOS_1((byte) 0x01), - /** - * The subscription is accepted and any received QoS will be sent to - * this subscription. - */ - GRANTED_QOS_2((byte) 0x02), - - // ERRORS - - /** - * The subscription is not accepted and the Server either does not - * wish to reveal the reason or none of the other Reason Codes - * apply. - */ - UNSPECIFIED_ERROR((byte) 0x80), - /** - * The SUBSCRIBE is valid but the Server does not accept it. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), - /** - * The Client is not authorized to make this subscription. - */ - NOT_AUTHORIZED((byte) 0x87), - /** - * The Topic Filter is correctly formed but is not allowed for this Client. - */ - TOPIC_FILTER_INVALID((byte) 0x8F), - /** - * The specified Packet Identifier is already in use. - */ - PACKET_IDENTIFIER_IN_USE((byte) 0x91), - /** - * An implementation or administrative imposed limit has been - * exceeded. - */ - QUOTA_EXCEEDED((byte) 0x97), - /** - * The Server does not support Shared Subscriptions for this Client. - */ - SHARED_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0x9E), - /** - * The Server does not support Subscription Identifiers; the subscription is not accepted. - */ - SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED((byte) 0xA1), - /** - * The Server does not support Wildcard Subscriptions; the - * subscription is not accepted. - */ - WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED((byte) 0xA2); - - private static final SubscribeAckReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(SubscribeAckReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new SubscribeAckReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull SubscribeAckReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/reason/code/UnsubscribeAckReasonCode.java b/src/main/java/com/ss/mqtt/broker/model/reason/code/UnsubscribeAckReasonCode.java deleted file mode 100644 index dcaacefa..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/reason/code/UnsubscribeAckReasonCode.java +++ /dev/null @@ -1,75 +0,0 @@ -package com.ss.mqtt.broker.model.reason.code; - -import javasabr.rlib.common.util.ObjectUtils; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.util.stream.Stream; - -@RequiredArgsConstructor -public enum UnsubscribeAckReasonCode { - /** - * The subscription is deleted. - */ - SUCCESS((byte) 0x00), - /** - * The subscription is accepted and the maximum QoS sent will be - * QoS 1. This might be a lower QoS than was requested. - */ - NO_SUBSCRIPTION_EXISTED((byte) 0x11), - - // ERRORS - - /** - * The unsubscribe could not be completed and the Server - * either does not wish to reveal the reason or none of the - * other Reason Codes apply. - */ - UNSPECIFIED_ERROR((byte) 0x80), - /** - * The UNSUBSCRIBE is valid but the Server does not accept it. - */ - IMPLEMENTATION_SPECIFIC_ERROR((byte) 0x83), - /** - * The Client is not authorized to unsubscribe. - */ - NOT_AUTHORIZED((byte) 0x87), - /** - * The Topic Filter is correctly formed but is not allowed for this Client. - */ - TOPIC_FILTER_INVALID((byte) 0x8F), - /** - * The specified Packet Identifier is already in use. - */ - PACKET_IDENTIFIER_IN_USE((byte) 0x91); - - private static final UnsubscribeAckReasonCode[] VALUES; - - static { - - var maxId = Stream.of(values()) - .mapToInt(UnsubscribeAckReasonCode::getValue) - .map(value -> Byte.toUnsignedInt((byte) value)) - .max() - .orElse(0); - - var values = new UnsubscribeAckReasonCode[maxId + 1]; - - for (var value : values()) { - values[Byte.toUnsignedInt(value.value)] = value; - } - - VALUES = values; - } - - public static @NotNull UnsubscribeAckReasonCode of(int index) { - return ObjectUtils.notNull( - VALUES[index], - index, - arg -> new IndexOutOfBoundsException("Doesn't support reason code: " + arg) - ); - } - - private @Getter final byte value; -} diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/SharedTopicFilter.java b/src/main/java/com/ss/mqtt/broker/model/topic/SharedTopicFilter.java deleted file mode 100644 index 37f7192c..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/topic/SharedTopicFilter.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.ss.mqtt.broker.model.topic; - -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -public class SharedTopicFilter extends TopicFilter { - - private final @Getter @NotNull String group; - - public SharedTopicFilter(@NotNull String topicFilter, @NotNull String group) { - super(topicFilter); - this.group = group; - } -} - diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/TopicFilter.java b/src/main/java/com/ss/mqtt/broker/model/topic/TopicFilter.java deleted file mode 100644 index 2655e284..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/topic/TopicFilter.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ss.mqtt.broker.model.topic; - -import lombok.NoArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@NoArgsConstructor -public class TopicFilter extends AbstractTopic { - - public TopicFilter(String topicFilter) { - super(topicFilter); - } -} - diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/TopicName.java b/src/main/java/com/ss/mqtt/broker/model/topic/TopicName.java deleted file mode 100644 index 84cdcb1b..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/topic/TopicName.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.ss.mqtt.broker.model.topic; - -import lombok.NoArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@NoArgsConstructor -public class TopicName extends AbstractTopic { - - public TopicName(String topicName) { - super(topicName); - } -} - diff --git a/src/main/java/com/ss/mqtt/broker/model/topic/package-info.java b/src/main/java/com/ss/mqtt/broker/model/topic/package-info.java deleted file mode 100644 index bf2d68ac..00000000 --- a/src/main/java/com/ss/mqtt/broker/model/topic/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.model.topic; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/network/MqttConnection.java b/src/main/java/com/ss/mqtt/broker/network/MqttConnection.java deleted file mode 100644 index 26e5ca7c..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/MqttConnection.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.ss.mqtt.broker.network; - -import com.ss.mqtt.broker.config.MqttConnectionConfig; -import com.ss.mqtt.broker.handler.packet.in.PacketInHandler; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.network.packet.MqttPacketReader; -import com.ss.mqtt.broker.network.packet.MqttPacketWriter; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import javasabr.rlib.network.BufferAllocator; -import javasabr.rlib.network.Connection; -import javasabr.rlib.network.Network; -import javasabr.rlib.network.impl.AbstractConnection; -import javasabr.rlib.network.packet.PacketReader; -import javasabr.rlib.network.packet.PacketWriter; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.log4j.Log4j2; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.channels.AsynchronousSocketChannel; -import java.util.function.Function; - -@Log4j2 -public class MqttConnection extends AbstractConnection { - - @Getter(AccessLevel.PROTECTED) - private final @NotNull PacketReader packetReader; - - @Getter(AccessLevel.PROTECTED) - private final @NotNull PacketWriter packetWriter; - - private final @Getter PacketInHandler @NotNull [] packetHandlers; - - private final @Getter @NotNull UnsafeMqttClient client; - private final @Getter @NotNull MqttConnectionConfig config; - - private volatile @Getter @Setter @NotNull MqttVersion mqttVersion; - private volatile @Getter @Setter @Nullable MqttSession session; - - public MqttConnection( - @NotNull Network> network, - @NotNull AsynchronousSocketChannel channel, - @NotNull BufferAllocator bufferAllocator, - int maxPacketsByRead, - PacketInHandler @NotNull [] packetHandlers, - @NotNull MqttConnectionConfig config, - @NotNull Function clientFactory - ) { - super(network, channel, bufferAllocator, maxPacketsByRead); - this.packetHandlers = packetHandlers; - this.config = config; - this.mqttVersion = MqttVersion.MQTT_3_1_1; - this.packetReader = createPacketReader(); - this.packetWriter = createPacketWriter(); - this.client = clientFactory.apply(this); - } - - public boolean isSupported(@NotNull MqttVersion mqttVersion) { - return this.mqttVersion.ordinal() >= mqttVersion.ordinal(); - } - - private @NotNull PacketReader createPacketReader() { - return new MqttPacketReader( - this, - channel, - bufferAllocator, - this::updateLastActivity, - this::handleReceivedPacket, - maxPacketsByRead - ); - } - - private @NotNull PacketWriter createPacketWriter() { - return new MqttPacketWriter( - this, - channel, - bufferAllocator, - this::updateLastActivity, - this::nextPacketToWrite, - this::onWrittenPacket, - this::onSentPacket - ); - } - - @Override - public @NotNull String toString() { - return getRemoteAddress(); - } - - @Override - protected void doClose() { - client.release().subscribe(); - super.doClose(); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/client/AbstractMqttClient.java b/src/main/java/com/ss/mqtt/broker/network/client/AbstractMqttClient.java deleted file mode 100644 index 9397ebcd..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/client/AbstractMqttClient.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.ss.mqtt.broker.network.client; - -import com.ss.mqtt.broker.config.MqttConnectionConfig; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.handler.client.MqttClientReleaseHandler; -import com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient; -import com.ss.mqtt.broker.factory.packet.out.MqttPacketOutFactory; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; -import lombok.Setter; -import lombok.extern.log4j.Log4j2; -import org.jspecify.annotations.Nullable; -import reactor.core.publisher.Mono; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; - -@Getter -@Log4j2 -public abstract class AbstractMqttClient implements UnsafeMqttClient { - - static { - DebugUtils.registerIncludedFields("clientId"); - } - - protected final MqttConnection connection; - protected final MqttClientReleaseHandler releaseHandler; - protected final AtomicBoolean released; - - private volatile @Setter String clientId; - private volatile @Setter @Getter @Nullable MqttSession session; - - private volatile long sessionExpiryInterval; - private volatile int receiveMax; - private volatile int maximumPacketSize; - private volatile int topicAliasMaximum; - private volatile int keepAlive; - - private volatile boolean requestResponseInformation = false; - private volatile boolean requestProblemInformation = false; - - public AbstractMqttClient(MqttConnection connection, MqttClientReleaseHandler releaseHandler) { - this.connection = connection; - this.releaseHandler = releaseHandler; - this.released = new AtomicBoolean(false); - this.clientId = StringUtils.EMPTY; - var config = connection.getConfig(); - this.sessionExpiryInterval = config.getDefaultSessionExpiryInterval(); - this.receiveMax = config.getReceiveMaximum(); - this.maximumPacketSize = config.getMaximumPacketSize(); - this.topicAliasMaximum = config.getTopicAliasMaximum(); - this.keepAlive = config.getMinKeepAliveTime(); - } - - @Override - public void handle(MqttReadablePacket packet) { - log.debug("Client [{}] received packet: {} : {}", clientId, packet.getName(), packet); - - var packetHandler = connection.getPacketHandlers()[packet.getPacketType()]; - - if (packetHandler != null) { - packetHandler.handle(this, packet); - } else { - log.warn("No packet handler in client {} for packet {}", this, packet); - } - } - - @Override - public void configure( - long sessionExpiryInterval, - int receiveMax, - int maximumPacketSize, - int topicAliasMaximum, - int keepAlive, - boolean requestResponseInformation, - boolean requestProblemInformation - ) { - this.sessionExpiryInterval = sessionExpiryInterval; - this.receiveMax = receiveMax; - this.maximumPacketSize = maximumPacketSize; - this.topicAliasMaximum = topicAliasMaximum; - this.keepAlive = keepAlive; - this.requestProblemInformation = requestProblemInformation; - this.requestResponseInformation = requestResponseInformation; - } - - @Override - public void send(MqttWritablePacket packet) { - log.debug("Send to client [{}] packet: {} : {}", clientId, packet.getName(), packet); - connection.send(packet); - } - - @Override - public CompletableFuture sendWithFeedback(MqttWritablePacket packet) { - log.debug("Send to client [{}] packet: {} : {}", clientId, packet.getName(), packet); - return connection.sendWithFeedback(packet); - } - - public void reject(ConnectAckReasonCode reasonCode) { - connection - .sendWithFeedback(getPacketOutFactory().newConnectAck(this, reasonCode)) - .thenAccept(sent -> connection.close()); - } - - @Override - public MqttPacketOutFactory getPacketOutFactory() { - return connection.getMqttVersion().getPacketOutFactory(); - } - - @Override - public MqttConnectionConfig getConnectionConfig() { - return connection.getConfig(); - } - - @Override - public Mono release() { - if (released.compareAndSet(false, true)) { - return releaseHandler.release(this); - } else { - return Mono.empty(); - } - } - - @Override - public String toString() { - return DebugUtils.toJsonString(this); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/client/ExternalMqttClient.java b/src/main/java/com/ss/mqtt/broker/network/client/ExternalMqttClient.java deleted file mode 100644 index 5c53a249..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/client/ExternalMqttClient.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.network.client; - -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.handler.client.MqttClientReleaseHandler; -import com.ss.mqtt.broker.util.DebugUtils; -import org.jetbrains.annotations.NotNull; - -public class ExternalMqttClient extends AbstractMqttClient { - - static { - DebugUtils.registerIncludedFields("clientId"); - } - - public ExternalMqttClient(@NotNull MqttConnection connection, @NotNull MqttClientReleaseHandler releaseHandler) { - super(connection, releaseHandler); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/client/InternalMqttClient.java b/src/main/java/com/ss/mqtt/broker/network/client/InternalMqttClient.java deleted file mode 100644 index eb0cba6c..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/client/InternalMqttClient.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.ss.mqtt.broker.network.client; - -import com.ss.mqtt.broker.handler.client.MqttClientReleaseHandler; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.util.DebugUtils; -import org.jetbrains.annotations.NotNull; - -public class InternalMqttClient extends AbstractMqttClient { - - static { - DebugUtils.registerIncludedFields("clientId"); - } - - public InternalMqttClient(@NotNull MqttConnection connection, @NotNull MqttClientReleaseHandler releaseHandler) { - super(connection, releaseHandler); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/client/MqttClient.java b/src/main/java/com/ss/mqtt/broker/network/client/MqttClient.java deleted file mode 100644 index 3dcdc2d5..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/client/MqttClient.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.ss.mqtt.broker.network.client; - -import com.ss.mqtt.broker.config.MqttConnectionConfig; -import com.ss.mqtt.broker.model.MqttSession; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.factory.packet.out.MqttPacketOutFactory; -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import reactor.core.publisher.Mono; - -import java.util.concurrent.CompletableFuture; - -public interface MqttClient { - - interface UnsafeMqttClient extends MqttClient { - - @NotNull MqttConnection getConnection(); - - void handle(@NotNull MqttReadablePacket packet); - - void configure( - long sessionExpiryInterval, - int receiveMax, - int maximumPacketSize, - int topicAliasMaximum, - int keepAlive, - boolean requestResponseInformation, - boolean requestProblemInformation - ); - - void setClientId(@NotNull String clientId); - - void setSession(@Nullable MqttSession session); - - void reject(@NotNull ConnectAckReasonCode reasonCode); - - @NotNull Mono release(); - } - - @NotNull MqttPacketOutFactory getPacketOutFactory(); - @NotNull MqttConnectionConfig getConnectionConfig(); - - @NotNull String getClientId(); - @Nullable MqttSession getSession(); - - int getKeepAlive(); - int getMaximumPacketSize(); - int getReceiveMax(); - int getTopicAliasMaximum(); - - long getSessionExpiryInterval(); - - void send(@NotNull MqttWritablePacket packet); - @NotNull CompletableFuture sendWithFeedback(@NotNull MqttWritablePacket packet); -} diff --git a/src/main/java/com/ss/mqtt/broker/network/client/package-info.java b/src/main/java/com/ss/mqtt/broker/network/client/package-info.java deleted file mode 100644 index 4d99fb2d..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/client/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.network.client; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketWriter.java b/src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketWriter.java deleted file mode 100644 index 3035747e..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/MqttPacketWriter.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.ss.mqtt.broker.network.packet; - -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket; -import com.ss.mqtt.broker.util.MqttDataUtils; - -import java.nio.ByteBuffer; -import java.nio.channels.AsynchronousSocketChannel; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import javasabr.rlib.network.BufferAllocator; -import javasabr.rlib.network.packet.WritablePacket; -import javasabr.rlib.network.packet.impl.AbstractPacketWriter; -import org.jspecify.annotations.Nullable; - -public class MqttPacketWriter extends AbstractPacketWriter { - - public MqttPacketWriter( - MqttConnection connection, - AsynchronousSocketChannel channel, - BufferAllocator bufferAllocator, - Runnable updateActivityFunction, - Supplier<@Nullable WritablePacket> nextWritePacketSupplier, - Consumer writtenPacketHandler, - BiConsumer sentPacketHandler - ) { - super( - connection, - channel, - bufferAllocator, - updateActivityFunction, - nextWritePacketSupplier, - writtenPacketHandler, - sentPacketHandler - ); - } - - @Override - protected int getTotalSize(WritablePacket packet, int expectedLength) { - return 1 + MqttDataUtils.sizeOfMbi(expectedLength) + expectedLength; - } - - @Override - protected boolean onBeforeWrite( - MqttWritablePacket packet, - int expectedLength, - int totalSize, - ByteBuffer firstBuffer, - ByteBuffer secondBuffer - ) { - firstBuffer.clear(); - secondBuffer.clear(); - return true; - } - - @Override - protected boolean onWrite( - MqttWritablePacket packet, - int expectedLength, - int totalSize, - ByteBuffer firstBuffer, - ByteBuffer secondBuffer - ) { - if (!packet.write(secondBuffer)) { - return false; - } else { - secondBuffer.flip(); - return true; - } - } - - @Override - protected boolean onAfterWrite( - MqttWritablePacket packet, - int expectedLength, - int totalSize, - ByteBuffer firstBuffer, - ByteBuffer secondBuffer - ) { - firstBuffer.put((byte) packet.getPacketTypeAndFlags()); - MqttDataUtils.writeMbi(secondBuffer.remaining(), firstBuffer); - firstBuffer.put(secondBuffer).flip(); - return true; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/PacketType.java b/src/main/java/com/ss/mqtt/broker/network/packet/PacketType.java deleted file mode 100644 index 52d5001d..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/PacketType.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.ss.mqtt.broker.network.packet; - -import org.jetbrains.annotations.NotNull; - -public enum PacketType { - RESERVED, - /** - * After a Network Connection is established by a Client to a Server, the first Packet sent from - * the Client to the Server MUST be a CONNECT Packet - */ - CONNECT, - /** - * The CONNACK Packet is the packet sent by the Server in response to a CONNECT Packet received from a Client. - * The first packet sent from the Server to the Client MUST be a CONNACK Packet [MQTT-3.2.0-1]. - */ - CONNECT_ACK, - /** - * A PUBLISH Control Packet is sent from a Client to a Server or from Server to a Client to - * transport an Application Message. - */ - PUBLISH, - /** - * A PUBACK Packet is the response to a PUBLISH Packet with QoS level 1. - */ - PUBLISH_ACK, - /** - * A PUBREC Packet is the response to a PUBLISH Packet with QoS 2. It is the second packet of the QoS 2 - * protocol exchange. - */ - PUBLISH_RECEIVED, - /** - * A PUBREL Packet is the response to a PUBREC Packet. It is the third packet of the QoS 2 protocol exchange. - */ - PUBLISH_RELEASED, - /** - * The PUBCOMP packet is the response to a PUBREL packet. It is the fourth and final packet of - * the QoS 2 protocol exchange. - */ - PUBLISH_COMPLETED, - /** - * The SUBSCRIBE Packet is sent from the Client to the Server to create one or more Subscriptions. - * Each Subscription registers a Client’s interest in one or more Topics. The Server sends PUBLISH Packets - * to the Client in order to forward Application Messages that were published to Topics that match - * these Subscriptions. The SUBSCRIBE Packet also specifies (for each Subscription) the maximum QoS with - * which the Server can send Application Messages to the Client. - */ - SUBSCRIBE, - /** - * A SUBACK Packet is sent by the Server to the Client to confirm receipt and processing of a SUBSCRIBE Packet. - */ - SUBSCRIBE_ACK, - /** - * An UNSUBSCRIBE Packet is sent by the Client to the Server, to unsubscribe from topics. - */ - UNSUBSCRIBE, - /** - * The UNSUBACK Packet is sent by the Server to the Client to confirm receipt of an UNSUBSCRIBE Packet. - */ - UNSUBSCRIBE_ACK, - /** - * The PINGREQ packet is sent from a Client to the Server. It can be used to: - *

- * • Indicate to the Server that the Client is alive in the absence of any other MQTT Control Packets being - * sent from the Client to the Server. - *

- * • Request that the Server responds to confirm that it is alive. - *

- * • Exercise the network to indicate that the Network Connection is active. - *

- * This packet is used in Keep Alive processing. Refer to section 3.1.2.10 for more details - */ - PING_REQUEST, - /** - * A PINGRESP Packet is sent by the Server to the Client in response to a PINGREQ packet. It indicates - * that the Server is alive. - */ - PING_RESPONSE, - /** - * The DISCONNECT packet is the final MQTT Control Packet sent from the Client or the Server. It - * indicates the reason why the Network Connection is being closed. The Client or Server MAY send a - * DISCONNECT packet before closing the Network Connection. If the Network Connection is closed - * without the Client first sending a DISCONNECT packet with Reason Code 0x00 (Normal disconnection) - * and the Connection has a Will Message, the Will Message is published. Refer to section 3.1.2.5 for - * further details. - */ - DISCONNECT, - /** - * An AUTH packet is sent from Client to Server or Server to Client as part of an extended authentication - * exchange, such as challenge / response authentication. It is a Protocol Error for the Client or Server to - * send an AUTH packet if the CONNECT packet did not contain the same Authentication Method. - */ - AUTHENTICATE, - - /** - * Not supported - */ - INVALID; - - private static final PacketType[] VALUES = values(); - - public static @NotNull PacketType fromByte(byte packetType) { - if (packetType < 0 || packetType > AUTHENTICATE.ordinal()) { - return INVALID; - } else { - return VALUES[packetType]; - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/AuthenticationInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/AuthenticationInPacket.java deleted file mode 100644 index a3e256f5..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/AuthenticationInPacket.java +++ /dev/null @@ -1,111 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Authentication exchange. - */ -@Getter -public class AuthenticationInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.AUTHENTICATE.ordinal(); - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol - Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more - information about extended authentication. - */ - PacketProperty.AUTHENTICATION_METHOD, - /* - Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication - Data more than once. The contents of this data are defined by the authentication method. Refer to - section 4.12 for more information about extended authentication. - */ - PacketProperty.AUTHENTICATION_DATA, - /* - Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is - human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. - - The sender MUST NOT send this property if it would increase the size of the AUTH packet beyond the - Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the - Reason String more than once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the AUTH packet - beyond the Maximum Packet Size specified by the receiver [MQTT-3.15.2-3]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private AuthenticateReasonCode reasonCode; - - // properties - private String reason; - private String authenticationMethod; - - private byte[] authenticationData; - - public AuthenticationInPacket(byte info) { - super(info); - this.reasonCode = AuthenticateReasonCode.SUCCESS; - this.reason = StringUtils.EMPTY; - this.authenticationMethod = StringUtils.EMPTY; - this.authenticationData = ArrayUtils.EMPTY_BYTE_ARRAY; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901219 - reasonCode = AuthenticateReasonCode.of(readUnsignedByte(buffer)); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, byte[] value) { - switch (property) { - case AUTHENTICATION_DATA: - authenticationData = value; - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - case AUTHENTICATION_METHOD: - authenticationMethod = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/DisconnectInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/DisconnectInPacket.java deleted file mode 100644 index 524a3a15..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/DisconnectInPacket.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Disconnect notification. - */ -@Getter -public class DisconnectInPacket extends MqttReadablePacket { - - public static final byte PACKET_TYPE = (byte) PacketType.DISCONNECT.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. - - The Session Expiry Interval MUST NOT be sent on a DISCONNECT by the Server [MQTT-3.14.2-2]. - - If the Session Expiry Interval in the CONNECT packet was zero, then it is a Protocol Error to set a non - zero Session Expiry Interval in the DISCONNECT packet sent by the Client. If such a non-zero Session - Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server - uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in - */ - PacketProperty.SESSION_EXPIRY_INTERVAL, - /* - The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet - beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to - include the Reason String more than once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT - packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-4]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY, - /* - The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another - 2601 server)} or 0x9D (Server moved) as described in section 4.13. - */ - PacketProperty.SERVER_REFERENCE - ); - - private DisconnectReasonCode reasonCode; - - // properties - private String reason; - private String serverReference; - - private long sessionExpiryInterval; - - public DisconnectInPacket(byte info) { - super(info); - this.reasonCode = DisconnectReasonCode.NORMAL_DISCONNECTION; - this.reason = StringUtils.EMPTY; - this.serverReference = StringUtils.EMPTY; - this.sessionExpiryInterval = MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readImpl(MqttConnection connection, ByteBuffer buffer) { - this.sessionExpiryInterval = connection.getClient().getSessionExpiryInterval(); - super.readImpl(connection, buffer); - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 - if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = DisconnectReasonCode.of(readUnsignedByte(buffer)); - } - } - - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - return connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining(); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, long value) { - switch (property) { - case SESSION_EXPIRY_INTERVAL: - sessionExpiryInterval = value; - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - case SERVER_REFERENCE: - serverReference = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/MqttReadablePacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/MqttReadablePacket.java deleted file mode 100644 index ec085fcc..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/MqttReadablePacket.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.exception.ConnectionRejectException; -import com.ss.mqtt.broker.exception.MalformedPacketMqttException; -import com.ss.mqtt.broker.exception.MqttException; -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.util.DebugUtils; -import com.ss.mqtt.broker.util.MqttDataUtils; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.network.packet.impl.AbstractReadablePacket; -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharsetDecoder; -import java.nio.charset.CodingErrorAction; -import java.nio.charset.StandardCharsets; -import java.util.Collections; -import java.util.Set; - -public abstract class MqttReadablePacket extends AbstractReadablePacket { - - static { - DebugUtils.registerIncludedFields("userProperties"); - } - - private static final MutableArray EMPTY_PROPERTIES = MutableArray.ofType(StringPair.class); - - @Getter - @RequiredArgsConstructor - private static class Utf8Decoder { - private final CharsetDecoder decoder; - private final ByteBuffer inBuffer; - private final CharBuffer outBuffer; - } - - private static final ThreadLocal LOCAL_DECODER = ThreadLocal.withInitial(() -> { - - var decoder = StandardCharsets.UTF_8.newDecoder() - .onMalformedInput(CodingErrorAction.REPORT) - .onUnmappableCharacter(CodingErrorAction.REPORT); - - return new Utf8Decoder(decoder, ByteBuffer.allocate(1024), CharBuffer.allocate(1024)); - }); - - - /** - * The list of user properties. - */ - protected @Getter @NotNull MutableArray userProperties; - - /** - * The happened exception during parsing this packet. - */ - protected @Getter @Nullable Exception exception; - - protected MqttReadablePacket(byte info) { - this.userProperties = EMPTY_PROPERTIES; - } - - public abstract byte getPacketType(); - - @Override - protected void readImpl(@NotNull MqttConnection connection, @NotNull ByteBuffer buffer) { - readVariableHeader(connection, buffer); - - if (isPropertiesSupported(connection, buffer)) { - readProperties(buffer); - } - - readPayload(connection, buffer); - } - - @Override - protected void handleException(@NotNull ByteBuffer buffer, @NotNull Exception exception) { - super.handleException(buffer, exception); - - if (!(exception instanceof MqttException)) { - exception = new ConnectionRejectException(exception, ConnectAckReasonCode.PROTOCOL_ERROR); - } - - this.exception = exception; - } - - protected boolean isPropertiesSupported(@NotNull MqttConnection connection, @NotNull ByteBuffer buffer) { - return connection.isSupported(MqttVersion.MQTT_5); - } - - protected void readVariableHeader(@NotNull MqttConnection connection, @NotNull ByteBuffer buffer) { - } - - protected void readProperties(@NotNull ByteBuffer buffer) { - readProperties(buffer, getAvailableProperties()); - } - - protected void readPayload(@NotNull MqttConnection connection, @NotNull ByteBuffer buffer) { - } - - protected void readProperties(@NotNull ByteBuffer buffer, @NotNull Set availableProperties) { - - var propertiesLength = MqttDataUtils.readMbi(buffer); - - if (propertiesLength == -1) { - throw new IllegalStateException("Can't read properties length."); - } else if(propertiesLength == 0) { - return; - } - - var lastPositionInBuffer = buffer.position() + propertiesLength; - - while (buffer.position() < lastPositionInBuffer) { - - var property = PacketProperty.of(readUnsignedByte(buffer)); - - if (!availableProperties.contains(property)) { - throw new IllegalStateException("Property: " + property + " is not available for this packet."); - } - - switch (property.getDataType()) { - case BYTE: - applyProperty(property, readUnsignedByte(buffer)); - break; - case SHORT: - applyProperty(property, readUnsignedShort(buffer)); - break; - case INTEGER: - applyProperty(property, readUnsignedInt(buffer)); - break; - case MULTI_BYTE_INTEGER: - applyProperty(property, MqttDataUtils.readMbi(buffer)); - break; - case UTF_8_STRING: - applyProperty(property, readString(buffer)); - break; - case UTF_8_STRING_PAIR: - applyProperty(property, new StringPair(readString(buffer), readString(buffer))); - break; - case BINARY: - applyProperty(property, readBytes(buffer)); - break; - default: - throw new IllegalArgumentException("Unsupported data type: " + property.getDataType()); - } - } - } - - protected @NotNull Set getAvailableProperties() { - return Collections.emptySet(); - } - - protected void applyProperty(@NotNull PacketProperty property, long value) { - } - - protected void applyProperty(@NotNull PacketProperty property, @NotNull String value) { - } - - protected void applyProperty(@NotNull PacketProperty property, @NotNull byte[] value) { - } - - protected void applyProperty(@NotNull PacketProperty property, @NotNull StringPair value) { - switch (property) { - case USER_PROPERTY: - if (userProperties == EMPTY_PROPERTIES) { - userProperties = MutableArray.ofType(StringPair.class); - } - userProperties.add(value); - break; - } - } - - protected int readUnsignedByte(@NotNull ByteBuffer buffer) { - return Byte.toUnsignedInt(buffer.get()); - } - - protected int readUnsignedShort(@NotNull ByteBuffer buffer) { - return Short.toUnsignedInt(buffer.getShort()); - } - - protected long readUnsignedInt(@NotNull ByteBuffer buffer) { - return Integer.toUnsignedLong(buffer.getInt()); - } - - @Override - protected @NotNull String readString(@NotNull ByteBuffer buffer) { - - var utf8Decoder = LOCAL_DECODER.get(); - var inBuffer = utf8Decoder.getInBuffer(); - - var stringLength = readShort(buffer) & 0xFFFF; - - if (stringLength > inBuffer.capacity()) { - throw new MalformedPacketMqttException(); - } - - var decoder = utf8Decoder.getDecoder(); - var outBuffer = utf8Decoder.getOutBuffer(); - - buffer.get(inBuffer.clear().array(), 0, stringLength); - - decoder.reset(); - - var result = decoder.decode(inBuffer.position(stringLength).flip(), outBuffer.clear(), true); - - if (result.isError()) { - throw new MalformedPacketMqttException(); - } - - return new String(inBuffer.array(), 0, stringLength, StandardCharsets.UTF_8); - } - - protected @NotNull byte[] readBytes(@NotNull ByteBuffer buffer) { - var data = new byte[readShort(buffer) & 0xFFFF]; - buffer.get(data); - return data; - } - - protected @NotNull byte[] readPayload(@NotNull ByteBuffer buffer) { - - var payloadSize = buffer.limit() - buffer.position(); - - if (payloadSize < 1) { - return ArrayUtils.EMPTY_BYTE_ARRAY; - } - - var data = new byte[payloadSize]; - buffer.get(data); - return data; - } - - protected void unexpectedProperty(@NotNull PacketProperty property) { - throw new IllegalArgumentException("Unsupported property: " + property); - } - - @Override - public @NotNull String toString() { - return DebugUtils.toJsonString(this); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PingRequestInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PingRequestInPacket.java deleted file mode 100644 index 1ae6e7cd..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PingRequestInPacket.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.network.packet.PacketType; - -/** - * PING request. - */ -public class PingRequestInPacket extends MqttReadablePacket { - - public static final byte PACKET_TYPE = (byte) PacketType.PING_REQUEST.ordinal(); - - public PingRequestInPacket(byte info) { - super(info); - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PingResponseInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PingResponseInPacket.java deleted file mode 100644 index b93c84e7..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PingResponseInPacket.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.network.packet.PacketType; - -/** - * PING response. - */ -public class PingResponseInPacket extends MqttReadablePacket { - - public static final byte PACKET_TYPE = (byte) PacketType.PING_RESPONSE.ordinal(); - - public PingResponseInPacket(byte info) { - super(info); - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishAckInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishAckInPacket.java deleted file mode 100644 index 525b6885..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishAckInPacket.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish acknowledgment (QoS 1). - */ -@Getter -public class PublishAckInPacket extends MqttReadablePacket implements HasPacketId { - - private static final int PACKET_TYPE = PacketType.PUBLISH_ACK.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and is not intended to be parsed by - the receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this property if it would increase the size of the PUBACK packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the PUBACK - packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.4.2-3]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private PublishAckReasonCode reasonCode; - private int packetId; - - // properties - private String reason; - - public PublishAckInPacket(byte info) { - super(info); - this.reasonCode = PublishAckReasonCode.SUCCESS; - this.reason = ""; - } - - @Override - public byte getPacketType() { - return (byte) PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718045 - packetId = readUnsignedShort(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901123 - if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = PublishAckReasonCode.of(readUnsignedByte(buffer)); - } - } - - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - return connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining(); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishCompleteInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishCompleteInPacket.java deleted file mode 100644 index f8d53db6..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishCompleteInPacket.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish complete (QoS 2 delivery part 3). - */ -@Getter -public class PublishCompleteInPacket extends MqttReadablePacket implements HasPacketId { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_COMPLETED.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private PublishCompletedReasonCode reasonCode; - private int packetId; - - // properties - private String reason; - - public PublishCompleteInPacket(byte info) { - super(info); - this.reasonCode = PublishCompletedReasonCode.SUCCESS; - this.reason = ""; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - super.readVariableHeader(connection, buffer); - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718083 - packetId = readUnsignedShort(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 - if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = PublishCompletedReasonCode.of(readUnsignedByte(buffer)); - } - } - - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 - return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishInPacket.java deleted file mode 100644 index 7acca3f7..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishInPacket.java +++ /dev/null @@ -1,370 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import static com.ss.mqtt.broker.util.TopicUtils.EMPTY_TOPIC_NAME; -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicName; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.topic.TopicName; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.IntArray; -import javasabr.rlib.collections.array.MutableIntArray; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.NumberUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish message. - */ -@Getter -public class PublishInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH.ordinal(); - - static { - DebugUtils.registerIncludedFields("topicName", "qos", "duplicate", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the value of the Payload Format Indicator, either of: - • 0 (0x00) Byte Indicates that the Payload is unspecified bytes, which is equivalent to not sending a - Payload Format Indicator. - • 1 (0x01) Byte Indicates that the Payload is UTF-8 Encoded Character Data. The UTF-8 data in - the Payload MUST be well-formed UTF-8 as defined by the Unicode specification [Unicode] - and restated in RFC 3629 [RFC3629]. - - A Server MUST send the Payload Format Indicator unaltered to all subscribers receiving the Application - 1563 Message [MQTT-3.3.2-4]. The receiver MAY validate that the Payload is of the format indicated, and if it - is not send a PUBACK, PUBREC, or DISCONNECT with Reason Code of 0x99 (Payload format invalid) - as described in section 4.13. Refer to section 5.4.9 for information about security issues in validating the - payload format. - */ - PacketProperty.PAYLOAD_FORMAT_INDICATOR, - /* - Followed by the Four Byte Integer representing the Message Expiry Interval. - - If present, the Four Byte value is the lifetime of the Application Message in seconds. If the Message - Expiry Interval has passed and the Server has not managed to start onward delivery to a matching - subscriber, then it MUST delete the copy of the message for that subscriber [MQTT-3.3.2-5]. - - If absent, the Application Message does not expire. - - The PUBLISH packet sent to a Client by the Server MUST contain a Message Expiry Interval set to the - received value minus the time that the Application Message has been waiting in the Server [MQTT-3.3.2- - 6]. Refer to section 4.1 for details and limitations of stored state. - */ - PacketProperty.MESSAGE_EXPIRY_INTERVAL, - /* - Followed by the Two Byte integer representing the Topic Alias value. It is a Protocol Error to include the - Topic Alias value more than once. - - A Topic Alias is an integer value that is used to identify the Topic instead of using the Topic Name. This - reduces the size of the PUBLISH packet, and is useful when the Topic Names are long and the same - Topic Names are used repetitively within a Network Connection. - - The sender decides whether to use a Topic Alias and chooses the value. It sets a Topic Alias mapping by - including a non-zero length Topic Name and a Topic Alias in the PUBLISH packet. The receiver - processes the PUBLISH as normal but also sets the specified Topic Alias mapping to this Topic Name. - - If a Topic Alias mapping has been set at the receiver, a sender can send a PUBLISH packet that contains - that Topic Alias and a zero length Topic Name. The receiver then treats the incoming PUBLISH as if it - had contained the Topic Name of the Topic Alias. - - A sender can modify the Topic Alias mapping by sending another PUBLISH in the same Network - Connection with the same Topic Alias value and a different non-zero length Topic Name. - - Topic Alias mappings exist only within a Network Connection and last only for the lifetime of that Network - Connection. A receiver MUST NOT carry forward any Topic Alias mappings from one Network - Connection to another [MQTT-3.3.2-7]. - - A Topic Alias of 0 is not permitted. A sender MUST NOT send a PUBLISH packet containing a Topic - Alias which has the value 0 [MQTT-3.3.2-8]. - - A Client MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum - value returned by the Server in the CONNACK packet [MQTT-3.3.2-9]. A Client MUST accept all Topic - Alias values greater than 0 and less than or equal to the Topic Alias Maximum value that it sent in the - CONNECT packet [MQTT-3.3.2-10]. - - A Server MUST NOT send a PUBLISH packet with a Topic Alias greater than the Topic Alias Maximum - value sent by the Client in the CONNECT packet [MQTT-3.3.2-11]. A Server MUST accept all Topic Alias - values greater than 0 and less than or equal to the Topic Alias Maximum value that it returned in the - CONNACK packet [MQTT-3.3.2-12]. - - The Topic Alias mappings used by the Client and Server are independent from each other. Thus, when a - Client sends a PUBLISH containing a Topic Alias value of 1 to a Server and the Server sends a PUBLISH - with a Topic Alias value of 1 to that Client they will in general be referring to different Topics. - */ - PacketProperty.TOPIC_ALIAS, - /* - Followed by a UTF-8 Encoded String which is used as the Topic Name for a response message. The - Response Topic MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-13]. The - Response Topic MUST NOT contain wildcard characters [MQTT-3.3.2-14]. It is a Protocol Error to include - the Response Topic more than once. The presence of a Response Topic identifies the Message as a - Request. - - Refer to section 4.10 for more information about Request / Response. - - The Server MUST send the Response Topic unaltered to all subscribers receiving the Application - Message [MQTT-3.3.2-15]. - */ - PacketProperty.RESPONSE_TOPIC, - /* - Followed by Binary Data. The Correlation Data is used by the sender of the Request Message to identify - which request the Response Message is for when it is received. It is a Protocol Error to include - Correlation Data more than once. If the Correlation Data is not present, the Requester does not require - any correlation data. - - The Server MUST send the Correlation Data unaltered to all subscribers receiving the Application - Message [MQTT-3.3.2-16]. The value of the Correlation Data only has meaning to the sender of the - Request Message and receiver of the Response Message. - */ - PacketProperty.CORRELATION_DATA, - /* - Followed by a UTF-8 String Pair. The User Property is allowed to appear multiple times to represent - multiple name, value pairs. The same name is allowed to appear more than once. - - The Server MUST send all User Properties unaltered in a PUBLISH packet when forwarding the - Application Message to a Client [MQTT-3.3.2-17]. The Server MUST maintain the order of User - Properties when forwarding the Application Message [MQTT-3.3.2-18]. - */ - PacketProperty.USER_PROPERTY, - /* - Followed by a Variable Byte Integer representing the identifier of the subscription. - - The Subscription Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the - Subscription Identifier has a value of 0. Multiple Subscription Identifiers will be included if the publication - is the result of a match to more than one subscription, in this case their order is not significant. - */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, - /* - Followed by a UTF-8 Encoded String describing the content of the Application Message. The Content - Type MUST be a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-19]. - It is a Protocol Error to include the Content Type more than once. The value of the Content Type is - defined by the sending and receiving application. - - A Server MUST send the Content Type unaltered to all subscribers receiving the Application Message - [MQTT-3.3.2-20]. - */ - PacketProperty.CONTENT_TYPE - ); - - /** - * This field indicates the level of assurance for delivery of an Application Message. The QoS levels are - * shown below. - *

- * 0 - 00 At most once delivery - * 1 --01 At least once delivery - * 2 - 10 Exactly once delivery - * - - 11 Reserved – must not be used - *

- * If the Server included a Maximum QoS in its CONNACK response to a Client and it receives a PUBLISH - * packet with a QoS greater than this, then it uses DISCONNECT with Reason Code 0x9B (QoS not - * supported) as described in section 4.13 Handling errors. - *

- * A PUBLISH Packet MUST NOT have both QoS bits set to 1 [MQTT-3.3.1-4]. If a Server or Client receives - * a PUBLISH packet which has both QoS bits set to 1 it is a Malformed Packet. Use DISCONNECT with - * Reason Code 0x81 (Malformed Packet) as described in - */ - private final QoS qos; - - /** - * If the DUP flag is set to 0, it indicates that this is the first occasion that the Client or Server has attempted - * to send this PUBLISH packet. If the DUP flag is set to 1, it indicates that this might be re-delivery of an - * earlier attempt to send the packet. - *

- * The DUP flag MUST be set to 1 by the Client or Server when it attempts to re-deliver a PUBLISH packet - * [MQTT-3.3.1-1]. The DUP flag MUST be set to 0 for all QoS 0 messages [MQTT-3.3.1-2]. - *

- * The value of the DUP flag from an incoming PUBLISH packet is not propagated when the PUBLISH - * packet is sent to subscribers by the Server. The DUP flag in the outgoing PUBLISH packet is set - * independently to the incoming PUBLISH packet, its value MUST be determined solely by whether the - * outgoing PUBLISH packet is a retransmission - */ - private final boolean duplicate; - - /** - * If the RETAIN flag is set to 1 in a PUBLISH packet sent by a Client to a Server, the Server MUST replace - * any existing retained message for this topic and store the Application Message [MQTT-3.3.1-5], so that it - * can be delivered to future subscribers whose subscriptions match its Topic Name. If the Payload contains - * zero bytes it is processed normally by the Server but any retained message with the same topic name - * MUST be removed and any future subscribers for the topic will not receive a retained message [MQTT1476 3.3.1-6]. - * A retained message with a Payload containing zero bytes MUST NOT be stored as a retained - * message on the Server [MQTT-3.3.1-7]. - *

- * If the RETAIN flag is 0 in a PUBLISH packet sent by a Client to a Server, the Server MUST NOT store the - * message as a retained message and MUST NOT remove or replace any existing retained message - * [MQTT-3.3.1-8]. - *

- * If the Server included Retain Available in its CONNACK response to a Client with its value set to 0 and it - * receives a PUBLISH packet with the RETAIN flag is set to 1, then it uses the DISCONNECT Reason - * Code of 0x9A (Retain not supported) as described in section 4.13. - *

- * When a new Non-shared Subscription is made, the last retained message, if any, on each matching topic - * name is sent to the Client as directed by the Retain Handling Subscription Option. These messages are - * sent with the RETAIN flag set to 1. Which retained messages are sent is controlled by the Retain - * Handling Subscription Option. At the time of the Subscription: - * • If Retain Handling is set to 0 the Server MUST send the retained messages matching the Topic - * Filter of the subscription to the Client [MQTT-3.3.1-9]. - * • If Retain Handling is set to 1 then if the subscription did not already exist, the Server MUST send - * all retained message matching the Topic Filter of the subscription to the Client, and if the - * subscription did exist the Server MUST NOT send the retained messages. [MQTT-3.3.1-10]. - * • If Retain Handling is set to 2, the Server MUST NOT send the retained messages - *

- * If the Server receives a PUBLISH packet with the RETAIN flag set to 1, and QoS 0 it SHOULD store the - * new QoS 0 message as the new retained message for that topic, but MAY choose to discard it at any - * time. If this happens there will be no retained message for that topic. - *

- * If the current retained message for a Topic expires, it is discarded and there will be no retained message - * for that topic. - *

- * The setting of the RETAIN flag in an Application Message forwarded by the Server from an established - * connection is controlled by the Retain As Published subscription option. Refer to section 3.8.3.1 for a - * definition of the Subscription Options. - * • If the value of Retain As Published subscription option is set to 0, the Server MUST set the RETAIN - * flag to 0 when forwarding an Application Message regardless of how the RETAIN flag was set in the - * received PUBLISH packet [MQTT-3.3.1-12]. - * • If the value of Retain As Published subscription option is set to 1, the Server MUST set the RETAIN - * flag equal to the RETAIN flag in the received PUBLISH packet - */ - private final boolean retained; - - /** - * The Topic Name identifies the information channel to which Payload data is published. - *

- * The Topic Name MUST be present as the first field in the PUBLISH packet Variable Header. It MUST be - * a UTF-8 Encoded String as defined in section 1.5.4 [MQTT-3.3.2-1]. - *

- * Standards Track Work Product Copyright © OASIS Open 2019. All Rights Reserved. Page 56 of 137 - * The Topic Name in the PUBLISH packet MUST NOT contain wildcard characters [MQTT-3.3.2-2]. - *

- * The Topic Name in a PUBLISH packet sent by a Server to a subscribing Client MUST match the - * Subscription’s Topic Filter according to the matching process defined in section 4.7 [MQTT-3.3.2-3]. - * However, as the Server is permitted to map the Topic Name to another name, it might not be the same as - * the Topic Name in the original PUBLISH packet. - *

- * To reduce the size of the PUBLISH packet the sender can use a Topic Alias. The Topic Alias is described - * in section 3.3.2.3.4. It is a Protocol Error if the Topic Name is zero length and there is no Topic Alias. - */ - private TopicName topicName; - - /** - * The Packet Identifier field is only present in PUBLISH packets where the QoS level is 1 or 2. Section - * 2.2.1 provides more information about Packet Identifiers. - */ - private int packetId; - - private byte[] payload; - - // properties - private String responseTopic; - private String contentType; - - private IntArray subscriptionIds; - - private byte[] correlationData; - - private long messageExpiryInterval = MqttPropertyConstants.MESSAGE_EXPIRY_INTERVAL_UNDEFINED; - private int topicAlias = MqttPropertyConstants.TOPIC_ALIAS_DEFAULT; - private boolean payloadFormatIndicator = MqttPropertyConstants.PAYLOAD_FORMAT_INDICATOR_DEFAULT; - - public PublishInPacket(byte info) { - super(info); - this.qos = QoS.of((info >> 1) & 0x03); - this.retained = NumberUtils.isSetBit(info, 0); - this.duplicate = NumberUtils.isSetBit(info, 3); - this.topicName = EMPTY_TOPIC_NAME; - this.responseTopic = StringUtils.EMPTY; - this.contentType = StringUtils.EMPTY; - this.correlationData = ArrayUtils.EMPTY_BYTE_ARRAY; - this.payload = ArrayUtils.EMPTY_BYTE_ARRAY; - this.subscriptionIds = IntArray.empty(); - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718039 - topicName = buildTopicName(readString(buffer)); - packetId = qos != QoS.AT_MOST_ONCE ? readUnsignedShort(buffer) : 0; - } - - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718040 - payload = readPayload(buffer); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, long value) { - switch (property) { - case PAYLOAD_FORMAT_INDICATOR: - payloadFormatIndicator = NumberUtils.toBoolean(value); - break; - case TOPIC_ALIAS: - topicAlias = NumberUtils.validate( - (int) value, - MqttPropertyConstants.TOPIC_ALIAS_MIN, - MqttPropertyConstants.TOPIC_ALIAS_MAX - ); - break; - case MESSAGE_EXPIRY_INTERVAL: - messageExpiryInterval = value; - break; - case SUBSCRIPTION_IDENTIFIER: - if (subscriptionIds == IntArray.empty()) { - subscriptionIds = ArrayFactory.mutableIntArray(); - } - if (subscriptionIds instanceof MutableIntArray array) { - array.add((int) value); - } - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(@NotNull PacketProperty property, @NotNull String value) { - switch (property) { - case RESPONSE_TOPIC: - // TODO should be validated - responseTopic = value; - break; - case CONTENT_TYPE: - contentType = value; - break; - default: - unexpectedProperty(property); - } - } - - @Override - protected void applyProperty(@NotNull PacketProperty property, @NotNull byte[] value) { - switch (property) { - case CORRELATION_DATA: - correlationData = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReceivedInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReceivedInPacket.java deleted file mode 100644 index b04ce94c..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReceivedInPacket.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish received (QoS 2 delivery part 1). - */ -@Getter -public class PublishReceivedInPacket extends MqttReadablePacket implements HasPacketId { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RECEIVED.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private PublishReceivedReasonCode reasonCode; - private int packetId; - - // properties - private String reason; - - public PublishReceivedInPacket(byte info) { - super(info); - this.reasonCode = PublishReceivedReasonCode.SUCCESS; - this.reason = StringUtils.EMPTY; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - super.readVariableHeader(connection, buffer); - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718050 - packetId = readUnsignedShort(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 - if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = PublishReceivedReasonCode.of(readUnsignedByte(buffer)); - } - } - - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901144 - return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReleaseInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReleaseInPacket.java deleted file mode 100644 index a3f9aafd..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/PublishReleaseInPacket.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish release (QoS 2 delivery part 2). - */ -@Getter -public class PublishReleaseInPacket extends MqttReadablePacket implements HasPacketId { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RELEASED.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private PublishReleaseReasonCode reasonCode; - private int packetId; - - // properties - private String reason; - - public PublishReleaseInPacket(byte info) { - super(info); - this.reasonCode = PublishReleaseReasonCode.SUCCESS; - this.reason = ""; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - super.readVariableHeader(connection, buffer); - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718055 - packetId = readUnsignedShort(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 - if (connection.isSupported(MqttVersion.MQTT_5) && buffer.hasRemaining()) { - reasonCode = PublishReleaseReasonCode.of(readUnsignedByte(buffer)); - } - } - - @Override - protected boolean isPropertiesSupported(MqttConnection connection, ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 - return super.isPropertiesSupported(connection, buffer) && buffer.hasRemaining(); - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeAckInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeAckInPacket.java deleted file mode 100644 index 6c1c782d..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeAckInPacket.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Subscribe acknowledgement. - */ -@Getter -public class SubscribeAckInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE_ACK.ordinal(); - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the - Client. - - The Server uses this value to give additional information to the Client. The Server MUST NOT send this - Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified - by the Client - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet - beyond the Maximum Packet Size specified by Client [MQTT-3.9.2-2]. The User Property is allowed to - appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more - than once. - */ - PacketProperty.USER_PROPERTY - ); - - private MutableArray reasonCodes; - private int packetId; - - // properties - private String reason; - - public SubscribeAckInPacket(byte info) { - super(info); - this.reasonCodes = ArrayFactory.mutableArray(SubscribeAckReasonCode.class); - this.reason = StringUtils.EMPTY; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718070 - packetId = readUnsignedShort(buffer); - } - - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718071 - if (buffer.remaining() < 1) { - throw new IllegalStateException("No any topic filters."); - } - - while (buffer.hasRemaining()) { - reasonCodes.add(SubscribeAckReasonCode.of(readUnsignedByte(buffer))); - } - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeInPacket.java deleted file mode 100644 index a87ffb72..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/SubscribeInPacket.java +++ /dev/null @@ -1,122 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter; - -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.model.SubscribeRetainHandling; -import com.ss.mqtt.broker.model.SubscribeTopicFilter; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.NumberUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Subscribe request. - */ -@Getter -public class SubscribeInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE.ordinal(); - - static { - DebugUtils.registerIncludedFields("packetId", "topicFilters"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription - Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a - value of 0. It is a Protocol Error to include the Subscription Identifier more than once. - - The Subscription Identifier is associated with any subscription created or modified as the result of this - SUBSCRIBE packet. If there is a Subscription Identifier, it is stored with the subscription. If this property is - not specified, then the absence of a Subscription Identifier is stored with the subscription. - */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, - /* - The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same - name is allowed to appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private MutableArray topicFilters; - private int packetId; - - // properties - private int subscriptionId; - - public SubscribeInPacket(byte info) { - super(info); - this.topicFilters = ArrayFactory.mutableArray(SubscribeTopicFilter.class); - this.subscriptionId = MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 - packetId = readUnsignedShort(buffer); - } - - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - - if (buffer.remaining() < 1) { - throw new IllegalStateException("No any topic filters."); - } - - boolean isMqtt5 = connection.isSupported(MqttVersion.MQTT_5); - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901168 - while (buffer.hasRemaining()) { - - var topicFilter = readString(buffer); - var options = readUnsignedByte(buffer); - - var qos = QoS.of(options & 0x03); - var retainHandling = isMqtt5 ? - SubscribeRetainHandling.of((options >> 4) & 0x03) : SubscribeRetainHandling.SEND; - - if (qos == QoS.INVALID || retainHandling == SubscribeRetainHandling.INVALID) { - throw new IllegalStateException("Unsupported qos or retain handling"); - } - - var noLocal = !isMqtt5 || NumberUtils.isSetBit(options, 2); - var rap = !isMqtt5 || NumberUtils.isSetBit(options, 3); - - topicFilters.add(new SubscribeTopicFilter(buildTopicFilter(topicFilter), qos, retainHandling, noLocal, rap)); - } - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, long value) { - switch (property) { - case SUBSCRIPTION_IDENTIFIER: - subscriptionId = (int) value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeAckInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeAckInPacket.java deleted file mode 100644 index 3c48bfcb..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeAckInPacket.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.MutableArray; -import javasabr.rlib.common.util.StringUtils; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Unsubscribe acknowledgement. - */ -@Getter -public class UnsubscribeAckInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE_ACK.ordinal(); - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the - Client. - - The Server uses this value to give additional information to the Client. The Server MUST NOT send this - Property if it would increase the size of the UNSUBACK packet beyond the Maximum Packet Size - specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK - packet beyond the Maximum Packet Size specified by the Client [MQTT-3.11.2-2]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private MutableArray reasonCodes; - private int packetId; - - // properties - private String reason; - - public UnsubscribeAckInPacket(byte info) { - super(info); - this.reasonCodes = ArrayFactory.mutableArray(UnsubscribeAckReasonCode.class); - this.reason = StringUtils.EMPTY; - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718079 - packetId = readUnsignedShort(buffer); - } - - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901194 - if (!connection.isSupported(MqttVersion.MQTT_5)) { - return; - } - - if (!buffer.hasRemaining()) { - return; - } - - reasonCodes = ArrayFactory.mutableArray(UnsubscribeAckReasonCode.class, buffer.remaining()); - - while (buffer.hasRemaining()) { - reasonCodes.add(UnsubscribeAckReasonCode.of(readUnsignedByte(buffer))); - } - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } - - @Override - protected void applyProperty(PacketProperty property, String value) { - switch (property) { - case REASON_STRING: - reason = value; - break; - default: - unexpectedProperty(property); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeInPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeInPacket.java deleted file mode 100644 index f3fde7d3..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/UnsubscribeInPacket.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.ss.mqtt.broker.network.packet.in; - -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.network.MqttConnection; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.collections.array.ArrayFactory; -import javasabr.rlib.collections.array.MutableArray; -import lombok.Getter; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Unsubscribe request. - */ -@Getter -public class UnsubscribeInPacket extends MqttReadablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE.ordinal(); - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same - name is allowed to appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private MutableArray topicFilters; - private int packetId; - - public UnsubscribeInPacket(byte info) { - super(info); - this.topicFilters = ArrayFactory.mutableArray(TopicFilter.class); - } - - @Override - public byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void readVariableHeader(MqttConnection connection, ByteBuffer buffer) { - packetId = readUnsignedShort(buffer); - } - - @Override - protected void readPayload(MqttConnection connection, ByteBuffer buffer) { - - if (buffer.remaining() < 1) { - throw new IllegalStateException("No any topic filters."); - } - - while (buffer.hasRemaining()) { - topicFilters.add(buildTopicFilter(readString(buffer))); - } - } - - @Override - protected Set getAvailableProperties() { - return AVAILABLE_PROPERTIES; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/in/package-info.java b/src/main/java/com/ss/mqtt/broker/network/packet/in/package-info.java deleted file mode 100644 index c5db0218..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/in/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.network.packet.in; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Authentication5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Authentication5OutPacket.java deleted file mode 100644 index bc065a66..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Authentication5OutPacket.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.collections.array.Array; -import lombok.RequiredArgsConstructor; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Authentication exchange. - */ -@RequiredArgsConstructor -public class Authentication5OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.AUTHENTICATE.ordinal(); - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by a UTF-8 Encoded String containing the name of the authentication method. It is a Protocol - Error to omit the Authentication Method or to include it more than once. Refer to section 4.12 for more - information about extended authentication. - */ - PacketProperty.AUTHENTICATION_METHOD, - /* - Followed by Binary Data containing authentication data. It is a Protocol Error to include Authentication - Data more than once. The contents of this data are defined by the authentication method. Refer to - section 4.12 for more information about extended authentication. - */ - PacketProperty.AUTHENTICATION_DATA, - /* - Followed by the UTF-8 Encoded String representing the reason for the disconnect. This Reason String is - human readable, designed for diagnostics and SHOULD NOT be parsed by the receiver. - - The sender MUST NOT send this property if it would increase the size of the AUTH packet beyond the - Maximum Packet Size specified by the receiver [MQTT-3.15.2-2]. It is a Protocol Error to include the - Reason String more than once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the AUTH packet - beyond the Maximum Packet Size specified by the receiver [MQTT-3.15.2-3]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - - private final AuthenticateReasonCode reasonCode; - - private final String reason; - private final String authenticateMethod; - - private final byte[] authenticateData; - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901219 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901221 - writeStringPairProperties( - buffer, - PacketProperty.USER_PROPERTY, - userProperties - ); - writeNotEmptyProperty( - buffer, - PacketProperty.REASON_STRING, - reason - ); - writeNotEmptyProperty( - buffer, - PacketProperty.AUTHENTICATION_METHOD, - authenticateMethod - ); - writeNotEmptyProperty( - buffer, - PacketProperty.AUTHENTICATION_DATA, - authenticateData - ); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Connect311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Connect311OutPacket.java deleted file mode 100644 index 0e4d2fdb..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Connect311OutPacket.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.MqttVersion; -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.common.util.ArrayUtils; -import javasabr.rlib.common.util.StringUtils; -import lombok.RequiredArgsConstructor; - -import java.nio.ByteBuffer; - -/** - * Connect request. - */ -@RequiredArgsConstructor -public class Connect311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.CONNECT.ordinal(); - - private final String username; - private final String willTopic; - private final String clientId; - - private final byte[] password; - private final byte[] willPayload; - - private final QoS willQos; - - private final int keepAlive; - - private final boolean willRetain; - private final boolean cleanStart; - - public Connect311OutPacket(String clientId, int keepAlive) { - this( - StringUtils.EMPTY, - StringUtils.EMPTY, - clientId, - ArrayUtils.EMPTY_BYTE_ARRAY, - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - false, - false - ); - } - - protected MqttVersion getMqttVersion() { - return MqttVersion.MQTT_3_1_1; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - - var mqttVersion = getMqttVersion(); - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718030 - writeString(buffer, mqttVersion.getName()); - writeByte(buffer, mqttVersion.getVersion()); - writeByte(buffer, buildConnectFlags()); - writeShort(buffer, keepAlive); - } - - @Override - protected void writePayload(ByteBuffer buffer) { - - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031 - writeString(buffer, clientId); - - if (StringUtils.isNotEmpty(willTopic)) { - appendWillProperties(buffer); - writeString(buffer, willTopic); - writeBytes(buffer, willPayload); - } - - if (StringUtils.isNotEmpty(username)) { - writeString(buffer, username); - } - - if (ArrayUtils.isNotEmpty(password)) { - writeBytes(buffer, password); - } - } - - private int buildConnectFlags() { - - int connectFlags = 0; - - if (StringUtils.isNotEmpty(username)) { - connectFlags |= 0b1000_0000; - } - - if (ArrayUtils.isNotEmpty(password)) { - connectFlags |= 0b0100_0000; - } - - if (StringUtils.isNotEmpty(willTopic)) { - connectFlags |= 0b0000_0100; - connectFlags |= (willQos.ordinal() << 3); - if (willRetain) { - connectFlags |= 0b0010_0000; - } - } - - if (cleanStart) { - connectFlags |= 0b0000_0010; - } - - return connectFlags; - } - - protected void appendWillProperties(ByteBuffer buffer) { } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck311OutPacket.java deleted file mode 100644 index 608815e9..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/ConnectAck311OutPacket.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Connect acknowledgment. - */ -@RequiredArgsConstructor -public class ConnectAck311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.CONNECT_ACK.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCode", "sessionPresent"); - } - - /** - * The values the Connect Reason Code are shown below. If a well formed CONNECT packet is received - * by the Server, but the Server is unable to complete the Connection the Server MAY send a CONNACK - * packet containing the appropriate Connect Reason code from this table. If a Server sends a CONNACK - * packet containing a Reason code of 128 or greater it MUST then close the Network Connection - */ - protected final @NotNull ConnectAckReasonCode reasonCode; - - /** - * The Session Present flag informs the Client whether the Server is using Session State from a - * previous connection for this ClientID. - * This allows the Client and Server to have a consistent view of the Session State. - * If the Server accepts a connection with Clean Start set to 1, the Server MUST set Session - * Present to 0 in the CONNACK packet in addition to setting a 0x00 (Success) Reason Code in the CONNACK packet - */ - private final boolean sessionPresent; - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035 - buffer.put((byte) (sessionPresent ? 0x01 : 0x00)); - buffer.put(getReasonCodeValue()); - } - - protected byte getReasonCodeValue() { - return reasonCode.getMqtt311(); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect311OutPacket.java deleted file mode 100644 index 6b5fac1f..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect311OutPacket.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; - -/** - * Disconnect notification. - */ -public class Disconnect311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.DISCONNECT.ordinal(); - - @Override - public int getExpectedLength() { - return 0; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect5OutPacket.java deleted file mode 100644 index 64b665be..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Disconnect5OutPacket.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode; -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import javasabr.rlib.collections.array.Array; -import lombok.RequiredArgsConstructor; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Disconnect notification. - */ -@RequiredArgsConstructor -public class Disconnect5OutPacket extends Disconnect311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - If the Session Expiry Interval is absent, the Session Expiry Interval in the CONNECT packet is used. - - The Session Expiry Interval MUST NOT be sent on a DISCONNECT by the Server [MQTT-3.14.2-2]. - - If the Session Expiry Interval in the CONNECT packet was zero, then it is a Protocol Error to set a non - zero Session Expiry Interval in the DISCONNECT packet sent by the Client. If such a non-zero Session - Expiry Interval is received by the Server, it does not treat it as a valid DISCONNECT packet. The Server - uses DISCONNECT with Reason Code 0x82 (Protocol Error) as described in - */ - PacketProperty.SESSION_EXPIRY_INTERVAL, - /* - The sender MUST NOT send this Property if it would increase the size of the DISCONNECT packet - beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-3]. It is a Protocol Error to - include the Reason String more than once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property may be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the DISCONNECT - packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.14.2-4]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY, - /* - The Server sends DISCONNECT including a Server Reference and Reason Code {0x9C (Use another - 2601 server)} or 0x9D (Server moved) as described in section 4.13. - */ - PacketProperty.SERVER_REFERENCE - ); - - private final DisconnectReasonCode reasonCode; - private final Array userProperties; - - private final String reason; - private final String serverReference; - - private final long sessionExpiryInterval; - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901207 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901209 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty( - buffer, - PacketProperty.REASON_STRING, - reason - ); - writeNotEmptyProperty( - buffer, - PacketProperty.SERVER_REFERENCE, - serverReference - ); - - if (sessionExpiryInterval != MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED) { - writeProperty( - buffer, - PacketProperty.SESSION_EXPIRY_INTERVAL, - sessionExpiryInterval, - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - ); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/MqttWritablePacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/MqttWritablePacket.java deleted file mode 100644 index 60e0f089..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/MqttWritablePacket.java +++ /dev/null @@ -1,222 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.util.DebugUtils; -import com.ss.mqtt.broker.util.MqttDataUtils; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.common.util.NumberUtils; -import javasabr.rlib.network.packet.impl.AbstractWritablePacket; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; -import java.nio.charset.StandardCharsets; - -@RequiredArgsConstructor -public abstract class MqttWritablePacket extends AbstractWritablePacket { - - private static final ThreadLocal LOCAL_BUFFER = - ThreadLocal.withInitial(() -> ByteBuffer.allocate(1024 * 1024)); - - @Override - protected void writeImpl(@NotNull ByteBuffer buffer) { - writeVariableHeader(buffer); - - if (isPropertiesSupported()) { - appendProperties(buffer); - } - - writePayload(buffer); - } - - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - } - - protected void writePayload(@NotNull ByteBuffer buffer) { - } - - protected boolean isPropertiesSupported() { - return false; - } - - protected void writeProperties(@NotNull ByteBuffer buffer) { - } - - public final int getPacketTypeAndFlags() { - - var type = getPacketType(); - var controlFlags = getPacketFlags(); - - return NumberUtils.setHighByteBits(controlFlags, type); - } - - protected byte getPacketType() { - throw new UnsupportedOperationException(); - } - - protected byte getPacketFlags() { - return 0; - } - - protected @NotNull ByteBuffer getPropertiesBuffer() { - return LOCAL_BUFFER.get().clear(); - } - - private void appendProperties(@NotNull ByteBuffer buffer) { - - var propertiesBuffer = getPropertiesBuffer(); - - writeProperties(propertiesBuffer); - - if (propertiesBuffer.position() < 1) { - buffer.put((byte) 0); - return; - } - - propertiesBuffer.flip(); - - MqttDataUtils.writeMbi(propertiesBuffer.limit(), buffer) - .put(propertiesBuffer); - } - - public void writeProperty(@NotNull ByteBuffer buffer, @NotNull PacketProperty property, boolean value) { - writeProperty(buffer, property, value ? 1 : 0); - } - - public void writeProperty( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - boolean value, - boolean def - ) { - if (value != def) { - writeProperty(buffer, property, value ? 1 : 0); - } - } - - public void writeProperty(@NotNull ByteBuffer buffer, @NotNull PacketProperty property, long value, long def) { - if (value != def) { - writeProperty(buffer, property, value); - } - } - - public void writeProperty(@NotNull ByteBuffer buffer, @NotNull PacketProperty property, long value) { - - buffer.put(property.getId()); - - switch (property.getDataType()) { - case BYTE: - writeByte(buffer, (int) value); - break; - case SHORT: - writeShort(buffer, (int) value); - break; - case INTEGER: - writeInt(buffer, (int) value); - break; - case MULTI_BYTE_INTEGER: - writeMbi(buffer, (int) value); - break; - default: - throw new IllegalArgumentException("Incorrect property type: " + property); - } - } - - public void writeProperty( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - @NotNull String value, - @NotNull String def - ) { - - if(!def.equals(value)) { - writeProperty(buffer, property, value); - } - } - - public void writeProperty( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - @NotNull StringPair value - ) { - - buffer.put(property.getId()); - writeString(buffer, value.getName()); - writeString(buffer, value.getValue()); - } - - public void writeNotEmptyProperty( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - @NotNull String value - ) { - - if (!value.isEmpty()) { - writeProperty(buffer, property, value); - } - } - - public void writeNotEmptyProperty( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - @NotNull byte[] value - ) { - - if (value.length > 0) { - writeProperty(buffer, property, value); - } - } - - public void writeProperty(@NotNull ByteBuffer buffer, @NotNull PacketProperty property, @NotNull String value) { - buffer.put(property.getId()); - writeString(buffer, value); - } - - public void writeProperty(@NotNull ByteBuffer buffer, @NotNull PacketProperty property, @NotNull byte[] value) { - buffer.put(property.getId()); - writeBytes(buffer, value); - } - - public void writeStringPairProperties( - @NotNull ByteBuffer buffer, - @NotNull PacketProperty property, - @NotNull Array pairs - ) { - - if (pairs.isEmpty()) { - return; - } - - for (var pair : pairs) { - buffer.put(property.getId()); - writeStringPair(buffer, pair); - } - } - - @Override - public void writeString(@NotNull ByteBuffer buffer, @NotNull String string) { - var bytes = string.getBytes(StandardCharsets.UTF_8); - buffer.putShort((short) bytes.length); - buffer.put(bytes); - } - - public void writeStringPair(@NotNull ByteBuffer buffer, @NotNull StringPair pair) { - writeString(buffer, pair.getName()); - writeString(buffer, pair.getValue()); - } - - public void writeMbi(@NotNull ByteBuffer buffer, int value) { - MqttDataUtils.writeMbi(value, buffer); - } - - public void writeBytes(@NotNull ByteBuffer buffer, @NotNull byte[] bytes) { - buffer.putShort((short) bytes.length); - buffer.put(bytes); - } - - @Override - public @NotNull String toString() { - return DebugUtils.toJsonString(this); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PingRequest311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PingRequest311OutPacket.java deleted file mode 100644 index 0ec86681..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PingRequest311OutPacket.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; - -/** - * PING request. - */ -public class PingRequest311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PING_REQUEST.ordinal(); - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PingResponse311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PingResponse311OutPacket.java deleted file mode 100644 index 5c4efeed..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PingResponse311OutPacket.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; - -/** - * PING response. - */ -public class PingResponse311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PING_RESPONSE.ordinal(); - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Publish311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Publish311OutPacket.java deleted file mode 100644 index 1212ca5f..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Publish311OutPacket.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.QoS; -import com.ss.mqtt.broker.util.DebugUtils; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -public class Publish311OutPacket extends PublishOutPacket { - - static { - DebugUtils.registerIncludedFields("qos", "topicName", "duplicate"); - } - - private final @NotNull QoS qos; - private final @NotNull byte[] payload; - private final @NotNull String topicName; - - private final boolean retained; - private final boolean duplicate; - - public Publish311OutPacket( - int packetId, - @NotNull QoS qos, - boolean retained, - boolean duplicate, - @NotNull String topicName, - @NotNull byte[] payload - ) { - super(packetId); - this.qos = qos; - this.retained = retained; - this.duplicate = duplicate; - this.payload = payload; - this.topicName = topicName; - } - - @Override - public int getExpectedLength() { - return 7 + payload.length; - } - - @Override - protected byte getPacketFlags() { - - byte info = (byte) (qos.ordinal() << 1); - - if (retained) { - info |= 0x01; - } - - if (duplicate) { - info |= 0x08; - } - - return info; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc384800412 - writeString(buffer, topicName); - if (qos.ordinal() > QoS.AT_MOST_ONCE.ordinal()) { - writeShort(buffer, packetId); - } - } - - @Override - protected void writePayload(@NotNull ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc384800413 - buffer.put(payload); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck311OutPacket.java deleted file mode 100644 index 62ca3f12..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck311OutPacket.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Publish acknowledgement. - */ -@RequiredArgsConstructor -public class PublishAck311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_ACK.ordinal(); - - /** - * Packet Identifier from the PUBLISH packet that is being acknowledged. - */ - private final int packetId; - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718045 - buffer.putShort((short) packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck5OutPacket.java deleted file mode 100644 index 28140a85..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishAck5OutPacket.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode; -import com.ss.mqtt.broker.model.data.type.StringPair; -import javasabr.rlib.collections.array.Array; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish acknowledgement. - */ -public class PublishAck5OutPacket extends PublishAck311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and is not intended to be parsed by - the receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this property if it would increase the size of the PUBACK packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.4.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The sender MUST NOT send this property if it would increase the size of the PUBACK - packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.4.2-3]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - private final String reason; - private final PublishAckReasonCode reasonCode; - - public PublishAck5OutPacket( - int packetId, - PublishAckReasonCode reasonCode, - Array userProperties, - String reason - ) { - super(packetId); - this.reasonCode = reasonCode; - this.userProperties = userProperties; - this.reason = reason; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - super.writeVariableHeader(buffer); - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901123 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901125 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty( - buffer, - PacketProperty.REASON_STRING, - reason - ); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete311OutPacket.java deleted file mode 100644 index 622e692b..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete311OutPacket.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Publish complete (QoS 2 delivery part 3). - */ -@RequiredArgsConstructor -public class PublishComplete311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_COMPLETED.ordinal(); - - private final int packetId; - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718083 - writeShort(buffer, packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete5OutPacket.java deleted file mode 100644 index 6cd88bfd..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishComplete5OutPacket.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode; -import com.ss.mqtt.broker.model.data.type.StringPair; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.common.util.StringUtils; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish complete (QoS 2 delivery part 3). - */ -public class PublishComplete5OutPacket extends PublishComplete311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - private final PublishCompletedReasonCode reasonCode; - private final String reason; - - public PublishComplete5OutPacket(int packetId, PublishCompletedReasonCode reasonCode) { - this(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); - } - - public PublishComplete5OutPacket( - int packetId, - PublishCompletedReasonCode reasonCode, - Array userProperties, - String reason - ) { - super(packetId); - this.reasonCode = reasonCode; - this.userProperties = userProperties; - this.reason = reason; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - super.writeVariableHeader(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901154 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - super.writeProperties(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901155 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishOutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishOutPacket.java deleted file mode 100644 index b9ac4500..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishOutPacket.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.HasPacketId; -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public abstract class PublishOutPacket extends MqttWritablePacket implements HasPacketId { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH.ordinal(); - - protected final @Getter int packetId; - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived311OutPacket.java deleted file mode 100644 index 3f065f3c..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived311OutPacket.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Publish received (QoS 2 delivery part 1). - */ -@RequiredArgsConstructor -public class PublishReceived311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RECEIVED.ordinal(); - - private final int packetId; - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718050 - writeShort(buffer, packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived5OutPacket.java deleted file mode 100644 index c129faea..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishReceived5OutPacket.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode; -import com.ss.mqtt.broker.model.data.type.StringPair; -import javasabr.rlib.collections.array.Array; -import javasabr.rlib.common.util.StringUtils; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Publish received (QoS 2 delivery part 1). - */ -public class PublishReceived5OutPacket extends PublishReceived311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - private final PublishReceivedReasonCode reasonCode; - private final String reason; - - public PublishReceived5OutPacket(int packetId, PublishReceivedReasonCode reasonCode) { - this(packetId, reasonCode, Array.empty(StringPair.class), StringUtils.EMPTY); - } - - public PublishReceived5OutPacket( - int packetId, - PublishReceivedReasonCode reasonCode, - Array userProperties, - String reason - ) { - super(packetId); - this.reasonCode = reasonCode; - this.userProperties = userProperties; - this.reason = reason; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - super.writeVariableHeader(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901143 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - super.writeProperties(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901135 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease311OutPacket.java deleted file mode 100644 index b4024944..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease311OutPacket.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Publish release (QoS 2 delivery part 2). - */ -@RequiredArgsConstructor -public class PublishRelease311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.PUBLISH_RELEASED.ordinal(); - - private final int packetId; - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected byte getPacketFlags() { - return 2; - } - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718055 - writeShort(buffer, packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease5OutPacket.java deleted file mode 100644 index 46d02fea..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/PublishRelease5OutPacket.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode; -import com.ss.mqtt.broker.model.data.type.StringPair; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; -import javasabr.rlib.collections.array.Array; - -/** - * Publish release (QoS 2 delivery part 2). - */ -public class PublishRelease5OutPacket extends PublishRelease311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is human readable, designed for diagnostics and SHOULD NOT be parsed by the - receiver. - - The sender uses this value to give additional information to the receiver. The sender MUST NOT send - this Property if it would increase the size of the PUBREL packet beyond the Maximum Packet Size - specified by the receiver [MQTT-3.6.2-2]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information for the PUBREL. The sender MUST NOT send this property if it would increase the size of the - PUBREL packet beyond the Maximum Packet Size specified by the receiver [MQTT-3.6.2-3]. The User - Property is allowed to appear multiple times to represent multiple name, value pairs. The same name is - allowed to appear more than once - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - private final PublishReleaseReasonCode reasonCode; - private final String reason; - - public PublishRelease5OutPacket( - int packetId, - PublishReleaseReasonCode reasonCode, - Array userProperties, - String reason - ) { - super(packetId); - this.userProperties = userProperties; - this.reasonCode = reasonCode; - this.reason = reason; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - super.writeVariableHeader(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901144 - writeByte(buffer, reasonCode.getValue()); - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - super.writeProperties(buffer); - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901145 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty(buffer, PacketProperty.REASON_STRING, reason); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe311OutPacket.java deleted file mode 100644 index 1e06357b..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe311OutPacket.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.SubscribeTopicFilter; -import com.ss.mqtt.broker.network.packet.PacketType; -import javasabr.rlib.collections.array.Array; -import lombok.RequiredArgsConstructor; - -import java.nio.ByteBuffer; - -/** - * Subscribe request. - */ -@RequiredArgsConstructor -public class Subscribe311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE.ordinal(); - - private final Array topicFilters; - private final int packetId; - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718065 - writeShort(buffer, packetId); - } - - @Override - protected void writePayload(ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718066 - for (var topicFilter : topicFilters) { - writeString(buffer, topicFilter.getTopicFilter().toString()); - writeByte(buffer, buildSubscriptionOptions(topicFilter)); - } - } - - protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { - return topicFilter.getQos().ordinal(); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe5OutPacket.java deleted file mode 100644 index 0fbcdf26..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/Subscribe5OutPacket.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.MqttPropertyConstants; -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.SubscribeTopicFilter; -import com.ss.mqtt.broker.model.data.type.StringPair; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; -import javasabr.rlib.collections.array.Array; - -/** - * Subscribe request. - */ -public class Subscribe5OutPacket extends Subscribe311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by a Variable Byte Integer representing the identifier of the subscription. The Subscription - Identifier can have the value of 1 to 268,435,455. It is a Protocol Error if the Subscription Identifier has a - value of 0. It is a Protocol Error to include the Subscription Identifier more than once. - - The Subscription Identifier is associated with any subscription created or modified as the result of this - SUBSCRIBE packet. If there is a Subscription Identifier, it is stored with the subscription. If this property is - not specified, then the absence of a Subscription Identifier is stored with the subscription. - */ - PacketProperty.SUBSCRIPTION_IDENTIFIER, - /* - The User Property is allowed to appear multiple times to represent multiple name, value pairs. The same - name is allowed to appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - // properties - private final Array userProperties; - private final int subscriptionId; - - public Subscribe5OutPacket(Array topicFilters, int packetId) { - this(topicFilters, packetId, Array.empty(StringPair.class), MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED); - } - - public Subscribe5OutPacket( - Array topicFilters, - int packetId, - Array userProperties, - int subscriptionId - ) { - super(topicFilters, packetId); - this.userProperties = userProperties; - this.subscriptionId = subscriptionId; - } - - protected int buildSubscriptionOptions(SubscribeTopicFilter topicFilter) { - - var subscriptionOptions = 0; - - subscriptionOptions |= topicFilter.getRetainHandling().ordinal() << 4; - - if (topicFilter.isRetainAsPublished()) { - subscriptionOptions |= 0b0000_1000; - } - - if (topicFilter.isNoLocal()) { - subscriptionOptions |= 0b0000_0100; - } - - subscriptionOptions |= topicFilter.getQos().ordinal(); - - return subscriptionOptions; - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901164 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeProperty( - buffer, - PacketProperty.SUBSCRIPTION_IDENTIFIER, - subscriptionId, - MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - ); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck311OutPacket.java deleted file mode 100644 index 85655421..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck311OutPacket.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.network.packet.PacketType; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.Array; -import lombok.RequiredArgsConstructor; - -import java.nio.ByteBuffer; - -/** - * Subscribe acknowledgement. - */ -@RequiredArgsConstructor -public class SubscribeAck311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.SUBSCRIBE_ACK.ordinal(); - - static { - DebugUtils.registerIncludedFields("reasonCodes", "packetId"); - } - - /** - * The order of Reason Codes in the SUBACK packet MUST match the order of Topic Filters in the SUBSCRIBE packet. - */ - private final Array reasonCodes; - - /** - * The Packet Identifier from the SUBSCRIBE. - */ - private final int packetId; - - @Override - public int getExpectedLength() { - return 2 + reasonCodes.size(); - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718070 - writeShort(buffer, packetId); - } - - @Override - protected void writePayload(ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718071 - for (var reasonCode : reasonCodes) { - writeByte(buffer, reasonCode.getValue()); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck5OutPacket.java deleted file mode 100644 index 0a02f30b..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/SubscribeAck5OutPacket.java +++ /dev/null @@ -1,78 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.util.DebugUtils; -import javasabr.rlib.collections.array.Array; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; - -/** - * Subscribe acknowledgement. - */ -public class SubscribeAck5OutPacket extends SubscribeAck311OutPacket { - - static { - DebugUtils.registerIncludedFields("reasonCodes", "packetId"); - } - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the - Client. - - The Server uses this value to give additional information to the Client. The Server MUST NOT send this - Property if it would increase the size of the SUBACK packet beyond the Maximum Packet Size specified - by the Client - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The Server MUST NOT send this property if it would increase the size of the SUBACK packet - beyond the Maximum Packet Size specified by Client [MQTT-3.9.2-2]. The User Property is allowed to - appear multiple times to represent multiple name, value pairs. The same name is allowed to appear more - than once. - */ - PacketProperty.USER_PROPERTY - ); - - private final Array userProperties; - private final String reason; - - public SubscribeAck5OutPacket( - int packetId, - Array reasonCodes, - Array userProperties, - String reason - ) { - super(reasonCodes, packetId); - this.userProperties = userProperties; - this.reason = reason; - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901174 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty( - buffer, - PacketProperty.REASON_STRING, - reason - ); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck311OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck311OutPacket.java deleted file mode 100644 index da7741c4..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck311OutPacket.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.network.packet.PacketType; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -/** - * Unsubscribe acknowledgement. - */ -@RequiredArgsConstructor -public class UnsubscribeAck311OutPacket extends MqttWritablePacket { - - private static final byte PACKET_TYPE = (byte) PacketType.UNSUBSCRIBE_ACK.ordinal(); - - private final int packetId; - - @Override - public int getExpectedLength() { - return 2; - } - - @Override - protected byte getPacketType() { - return PACKET_TYPE; - } - - @Override - protected void writeVariableHeader(@NotNull ByteBuffer buffer) { - // http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718074 - writeShort(buffer, packetId); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck5OutPacket.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck5OutPacket.java deleted file mode 100644 index 7b25b888..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/UnsubscribeAck5OutPacket.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.ss.mqtt.broker.network.packet.out; - -import com.ss.mqtt.broker.model.PacketProperty; -import com.ss.mqtt.broker.model.data.type.StringPair; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; - -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Set; -import javasabr.rlib.collections.array.Array; - -/** - * Unsubscribe acknowledgement. - */ -public class UnsubscribeAck5OutPacket extends UnsubscribeAck311OutPacket { - - private static final Set AVAILABLE_PROPERTIES = EnumSet.of( - /* - Followed by the UTF-8 Encoded String representing the reason associated with this response. This - Reason String is a human readable string designed for diagnostics and SHOULD NOT be parsed by the - Client. - - The Server uses this value to give additional information to the Client. The Server MUST NOT send this - Property if it would increase the size of the UNSUBACK packet beyond the Maximum Packet Size - specified by the Client [MQTT-3.11.2-1]. It is a Protocol Error to include the Reason String more than - once. - */ - PacketProperty.REASON_STRING, - /* - Followed by UTF-8 String Pair. This property can be used to provide additional diagnostic or other - information. The Server MUST NOT send this property if it would increase the size of the UNSUBACK - packet beyond the Maximum Packet Size specified by the Client [MQTT-3.11.2-2]. The User Property is - allowed to appear multiple times to represent multiple name, value pairs. The same name is allowed to - appear more than once. - */ - PacketProperty.USER_PROPERTY - ); - - private final Array reasonCodes; - private final Array userProperties; - private final String reason; - - public UnsubscribeAck5OutPacket( - int packetId, - Array reasonCodes, - Array userProperties, - String reason - ) { - super(packetId); - this.reasonCodes = reasonCodes; - this.userProperties = userProperties; - this.reason = reason; - } - - @Override - protected boolean isPropertiesSupported() { - return true; - } - - @Override - public int getExpectedLength() { - return -1; - } - - @Override - protected void writeProperties(ByteBuffer buffer) { - - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901182 - writeStringPairProperties(buffer, PacketProperty.USER_PROPERTY, userProperties); - writeNotEmptyProperty( - buffer, - PacketProperty.REASON_STRING, - reason - ); - } - - @Override - protected void writePayload(ByteBuffer buffer) { - // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901185 - for (var reasonCode : reasonCodes) { - writeByte(buffer, reasonCode.getValue()); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/out/package-info.java b/src/main/java/com/ss/mqtt/broker/network/packet/out/package-info.java deleted file mode 100644 index 1750df5f..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/out/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.network.packet.out; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/network/packet/package-info.java b/src/main/java/com/ss/mqtt/broker/network/packet/package-info.java deleted file mode 100644 index 096d24c3..00000000 --- a/src/main/java/com/ss/mqtt/broker/network/packet/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.network.packet; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/service/AuthenticationService.java b/src/main/java/com/ss/mqtt/broker/service/AuthenticationService.java deleted file mode 100644 index 8262e94b..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/AuthenticationService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ss.mqtt.broker.service; - -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public interface AuthenticationService { - - @NotNull Mono auth(@NotNull String userName, @NotNull byte[] password); - -} diff --git a/src/main/java/com/ss/mqtt/broker/service/ClientIdRegistry.java b/src/main/java/com/ss/mqtt/broker/service/ClientIdRegistry.java deleted file mode 100644 index a905ba99..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/ClientIdRegistry.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ss.mqtt.broker.service; - -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public interface ClientIdRegistry { - - @NotNull Mono register(@NotNull String clientId); - @NotNull Mono unregister(@NotNull String clientId); - - boolean validate(@NotNull String clientId); - - @NotNull Mono generate(); -} diff --git a/src/main/java/com/ss/mqtt/broker/service/CredentialSource.java b/src/main/java/com/ss/mqtt/broker/service/CredentialSource.java deleted file mode 100644 index 28c36e31..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/CredentialSource.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.ss.mqtt.broker.service; - -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public interface CredentialSource { - - @NotNull Mono check(@NotNull String user, @NotNull byte[] pass); - - @NotNull Mono check(@NotNull byte[] pass); -} diff --git a/src/main/java/com/ss/mqtt/broker/service/MqttSessionService.java b/src/main/java/com/ss/mqtt/broker/service/MqttSessionService.java deleted file mode 100644 index d4c71ffc..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/MqttSessionService.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.ss.mqtt.broker.service; - -import com.ss.mqtt.broker.model.MqttSession; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -public interface MqttSessionService { - - @NotNull Mono restore(@NotNull String clientId); - - @NotNull Mono create(@NotNull String clientId); - - @NotNull Mono store(@NotNull String clientId, @NotNull MqttSession session, long expiryInterval); -} diff --git a/src/main/java/com/ss/mqtt/broker/service/PublishingService.java b/src/main/java/com/ss/mqtt/broker/service/PublishingService.java deleted file mode 100644 index 28aa887b..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/PublishingService.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.ss.mqtt.broker.service; - -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import org.jetbrains.annotations.NotNull; - -public interface PublishingService { - - void publish(@NotNull MqttClient client, @NotNull PublishInPacket publish); -} diff --git a/src/main/java/com/ss/mqtt/broker/service/SubscriptionService.java b/src/main/java/com/ss/mqtt/broker/service/SubscriptionService.java deleted file mode 100644 index 2015dd4e..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/SubscriptionService.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.ss.mqtt.broker.service; - -import com.ss.mqtt.broker.model.*; -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode; -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode; -import com.ss.mqtt.broker.model.topic.TopicFilter; -import com.ss.mqtt.broker.model.topic.TopicName; -import com.ss.mqtt.broker.network.client.MqttClient; -import java.util.function.BiFunction; -import javasabr.rlib.collections.array.Array; -import org.jetbrains.annotations.NotNull; - -/** - * Subscription service - */ -public interface SubscriptionService { - - /** - * Runs function for each topic subscriber - * - * @param topicName topic name - * @param argument additional argument - * @param action function to run - * @return {@link ActionResult} of function - */ - @NotNull ActionResult forEachTopicSubscriber( - @NotNull TopicName topicName, - @NotNull A argument, - @NotNull BiFunction action - ); - - /** - * Adds MQTT client to topic filter subscribers - * - * @param mqttClient MQTT client to be added - * @param topicFilters topic filters - * @return array of subscribe ack reason codes - */ - @NotNull Array subscribe( - @NotNull MqttClient mqttClient, - @NotNull Array topicFilters - ); - - /** - * Removes MQTT client from subscribers by array of topic names - * - * @param mqttClient MQTT client to be removed - * @param topicFilters topic filters - * @return array of unsubscribe ack reason codes - */ - @NotNull Array unsubscribe( - @NotNull MqttClient mqttClient, - @NotNull Array topicFilters - ); - - void cleanSubscriptions(@NotNull MqttClient mqttClient, @NotNull MqttSession mqttSession); - - void restoreSubscriptions(@NotNull MqttClient mqttClient, @NotNull MqttSession mqttSession); -} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/AbstractCredentialSource.java b/src/main/java/com/ss/mqtt/broker/service/impl/AbstractCredentialSource.java deleted file mode 100644 index 22b80fc7..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/impl/AbstractCredentialSource.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.ss.mqtt.broker.service.impl; - -import com.ss.mqtt.broker.service.CredentialSource; -import javasabr.rlib.collections.dictionary.DictionaryFactory; -import javasabr.rlib.collections.dictionary.LockableRefToRefDictionary; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -import java.util.Arrays; - -public abstract class AbstractCredentialSource implements CredentialSource { - - private final LockableRefToRefDictionary credentials = - DictionaryFactory.stampedLockBasedRefToRefDictionary(String.class, byte[].class); - - abstract void init(); - - void putAll(@NotNull RefToRefDictionary creds) { - long stamp = credentials.writeLock(); - try { - credentials.append(creds); - } finally { - credentials.writeUnlock(stamp); - } - } - - void put(@NotNull String user, @NotNull byte[] pass) { - long stamp = credentials.writeLock(); - try { - credentials.put(user, pass); - } finally { - credentials.writeUnlock(stamp); - } - } - - @Override - public @NotNull Mono check(@NotNull String user, @NotNull byte[] pass) { - return Mono.just(Arrays.equals(pass, credentials.get(user))); - } - - @Override - public @NotNull Mono check(@NotNull byte[] pass) { - return Mono.just(Boolean.FALSE); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/DefaultPublishingService.java b/src/main/java/com/ss/mqtt/broker/service/impl/DefaultPublishingService.java deleted file mode 100644 index c16eba2a..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/impl/DefaultPublishingService.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.ss.mqtt.broker.service.impl; - -import com.ss.mqtt.broker.handler.publish.in.PublishInHandler; -import com.ss.mqtt.broker.network.client.MqttClient; -import com.ss.mqtt.broker.network.packet.in.PublishInPacket; -import com.ss.mqtt.broker.service.PublishingService; -import lombok.RequiredArgsConstructor; -import org.jetbrains.annotations.NotNull; - -@RequiredArgsConstructor -public class DefaultPublishingService implements PublishingService { - - private final PublishInHandler @NotNull [] publishInHandlers; - - @Override - public void publish(@NotNull MqttClient client, @NotNull PublishInPacket publish) { - publishInHandlers[publish.getQos().ordinal()].handle(client, publish); - } -} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/FileCredentialsSource.java b/src/main/java/com/ss/mqtt/broker/service/impl/FileCredentialsSource.java deleted file mode 100644 index 4bf6f4ab..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/impl/FileCredentialsSource.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.ss.mqtt.broker.service.impl; - -import com.ss.mqtt.broker.exception.CredentialsSourceException; -import javasabr.rlib.collections.dictionary.DictionaryCollectors; -import javasabr.rlib.collections.dictionary.RefToRefDictionary; -import org.jetbrains.annotations.NotNull; - -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Properties; - -public class FileCredentialsSource extends AbstractCredentialSource { - - private final @NotNull String fileName; - - public FileCredentialsSource(@NotNull String fileName) { - this.fileName = fileName; - init(); - } - - @Override - void init() { - var credentialUrl = FileCredentialsSource.class.getClassLoader().getResource(fileName); - if (credentialUrl == null) { - throw new CredentialsSourceException("Credentials file could not be found"); - } - try { - var credentialsProperties = new Properties(); - credentialsProperties.load(new FileInputStream(credentialUrl.getPath())); - - RefToRefDictionary creds = credentialsProperties.entrySet() - .stream() - .collect(DictionaryCollectors.toRefToRefDictionary( - entry -> entry.getKey().toString(), - entry -> entry.getValue().toString().getBytes(StandardCharsets.UTF_8) - )); - - putAll(creds); - - } catch (IOException e) { - throw new CredentialsSourceException(e); - } - } -} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/SimpleAuthenticationService.java b/src/main/java/com/ss/mqtt/broker/service/impl/SimpleAuthenticationService.java deleted file mode 100644 index 04d6918c..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/impl/SimpleAuthenticationService.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.ss.mqtt.broker.service.impl; - -import com.ss.mqtt.broker.service.AuthenticationService; -import com.ss.mqtt.broker.service.CredentialSource; -import lombok.RequiredArgsConstructor; -import reactor.core.publisher.Mono; - -@RequiredArgsConstructor -public class SimpleAuthenticationService implements AuthenticationService { - - private final CredentialSource credentialSource; - private final boolean allowAnonymousAuth; - - @Override - public Mono auth(String userName, byte[] password) { - if (allowAnonymousAuth && userName.isEmpty()) { - return Mono.just(Boolean.TRUE); - } else { - return credentialSource.check(userName, password); - } - } - -} diff --git a/src/main/java/com/ss/mqtt/broker/service/impl/package-info.java b/src/main/java/com/ss/mqtt/broker/service/impl/package-info.java deleted file mode 100644 index 1159ccaa..00000000 --- a/src/main/java/com/ss/mqtt/broker/service/impl/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -@NullMarked -package com.ss.mqtt.broker.service.impl; - -import org.jspecify.annotations.NullMarked; \ No newline at end of file diff --git a/src/main/java/com/ss/mqtt/broker/util/MqttDataUtils.java b/src/main/java/com/ss/mqtt/broker/util/MqttDataUtils.java deleted file mode 100644 index cd1eceed..00000000 --- a/src/main/java/com/ss/mqtt/broker/util/MqttDataUtils.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.ss.mqtt.broker.util; - -import org.jetbrains.annotations.NotNull; - -import java.nio.ByteBuffer; - -public class MqttDataUtils { - - public static int MAX_MBI = 268_435_455; - - /** - * Write a MQTT multi-byte integer to byte buffer. - * https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011 - * - * @throws IllegalArgumentException if number is too big. - */ - public static @NotNull ByteBuffer writeMbi(int number, @NotNull ByteBuffer buffer) { - - var sizeInBytes = 0; - var valueToWrite = number; - do { - - var digit = (byte) (valueToWrite % 128); - valueToWrite = valueToWrite / 128; - - if (valueToWrite > 0) { - digit |= 0x80; - } - - buffer.put(digit); - sizeInBytes++; - - } while (valueToWrite > 0); - - if (sizeInBytes > 4) { - throw new IllegalArgumentException(number + " is too big."); - } - - return buffer; - } - - /** - * Read a MQTT multi-byte integer from byte buffer. - * https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901011 - * - * @return -1 if buffer's data isn't enough to read integer. - */ - public static int readMbi(@NotNull ByteBuffer buffer) { - - var originalPos = buffer.position(); - - int result = 0; - int multiplier = 1; - - byte readValue; - do { - - if (!buffer.hasRemaining()) { - buffer.position(originalPos); - return -1; - } - - readValue = buffer.get(); - result += ((readValue & 0x7F) * multiplier); - multiplier *= 128; - - } while ((readValue & 0x80) != 0); - - return result; - } - - /** - * Get byte count of MQTT multi-byte integer. - */ - public static int sizeOfMbi(int number) { - - var sizeInBytes = 0; - var valueToWrite = number; - do { - valueToWrite = valueToWrite / 128; - sizeInBytes++; - } while (valueToWrite > 0); - - return sizeInBytes; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/util/ReactorUtils.java b/src/main/java/com/ss/mqtt/broker/util/ReactorUtils.java deleted file mode 100644 index 672f8a8d..00000000 --- a/src/main/java/com/ss/mqtt/broker/util/ReactorUtils.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.ss.mqtt.broker.util; - -import javasabr.rlib.common.util.StringUtils; -import org.jetbrains.annotations.NotNull; -import reactor.core.publisher.Mono; - -import java.util.function.BiFunction; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Supplier; - -public class ReactorUtils { - - public static Function ifTrue(Runnable function) { - return value -> { - if (value) { - function.run(); - } - return value; - }; - } - - public static Function ifTrue(@NotNull A arg, Consumer function) { - return value -> { - if (value) { - function.accept(arg); - } - return value; - }; - } - - public static Function> ifNotEmpty( - Function> toContinue, - Supplier> another - ) { - return value -> { - if (StringUtils.isNotEmpty(value)) { - return toContinue.apply(value); - } else { - return another.get(); - } - }; - } - - public static Function> ifTrue( - Supplier> function, - Runnable another - ) { - return value -> { - - if (!value) { - another.run(); - return Mono.empty(); - } - - return function.get(); - }; - } - - public static Function> ifTrue( - T1 arg1, - T2 arg2, - BiFunction> function, - T3 arg3, - Consumer another - ) { - return value -> { - - if (value) { - return function.apply(arg1, arg2); - } - - another.accept(arg3); - return Mono.empty(); - }; - } -} diff --git a/src/main/java/com/ss/mqtt/broker/util/SubscriberUtils.java b/src/main/java/com/ss/mqtt/broker/util/SubscriberUtils.java deleted file mode 100644 index 74a66bc0..00000000 --- a/src/main/java/com/ss/mqtt/broker/util/SubscriberUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.ss.mqtt.broker.util; - -import com.ss.mqtt.broker.model.SharedSubscriber; -import com.ss.mqtt.broker.model.SingleSubscriber; -import com.ss.mqtt.broker.model.Subscriber; -import com.ss.mqtt.broker.network.client.MqttClient; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class SubscriberUtils { - - private static boolean isSharedSubscriber(@NotNull Subscriber subscriber) { - return subscriber instanceof SharedSubscriber; - } - - private static boolean isSingleSubscriber(@NotNull Subscriber subscriber) { - return subscriber instanceof SingleSubscriber; - } - - public static boolean isSharedSubscriberWithGroup(@NotNull String group, @NotNull Subscriber subscriber) { - return isSharedSubscriber(subscriber) && group.equals(((SharedSubscriber) subscriber).getGroup()); - } - - public static @Nullable MqttClient singleSubscriberToMqttClient(@NotNull Subscriber subscriber) { - return isSingleSubscriber(subscriber) ? ((SingleSubscriber) subscriber).getMqttClient() : null; - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/extension/SpecificationExtensions.groovy b/src/test/groovy/com/ss/mqtt/broker/test/extension/SpecificationExtensions.groovy deleted file mode 100644 index 67878cc2..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/extension/SpecificationExtensions.groovy +++ /dev/null @@ -1,78 +0,0 @@ -package com.ss.mqtt.broker.test.extension - -import com.ss.mqtt.broker.model.data.type.PacketDataType -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket -import com.ss.mqtt.broker.util.MqttDataUtils -import javasabr.rlib.collections.array.Array -import javasabr.rlib.collections.array.IntArray -import spock.lang.Specification - -import java.nio.ByteBuffer - -class SpecificationExtensions extends Specification { - - static final writer = new MqttWritablePacket() { - - @Override - protected void writeImpl(ByteBuffer buffer) {} - } - - static ByteBuffer putMbi(ByteBuffer self, int value) { - MqttDataUtils.writeMbi(value, self) - return self - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, boolean value) { - return putProperty(self, property, value ? 1 : 0) - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, long value) { - writer.writeProperty(self, property, value) - return self - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, byte[] value) { - writer.writeProperty(self, property, value) - return self - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, String value) { - writer.writeProperty(self, property, value) - return self - } - - static ByteBuffer putString(ByteBuffer self, String value) { - writer.writeString(self, value) - return self - } - - static ByteBuffer putBytes(ByteBuffer self, byte[] value) { - writer.writeBytes(self, value) - return self - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, Array values) { - - switch (property.getDataType()) { - case PacketDataType.UTF_8_STRING_PAIR: - writer.writeStringPairProperties(self, property, values as Array) - break - default: - throw new IllegalStateException() - } - - return self - } - - static ByteBuffer putProperty(ByteBuffer self, PacketProperty property, IntArray values) { - values.each { writer.writeProperty(self, property, it) } - return self - } - - static ByteBuffer putBoolean(ByteBuffer self, boolean value) { - self.put((value ? 1 : 0) as byte) - return self - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/ConnectSubscribePublishTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/ConnectSubscribePublishTest.groovy deleted file mode 100644 index 91658353..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/ConnectSubscribePublishTest.groovy +++ /dev/null @@ -1,264 +0,0 @@ -package com.ss.mqtt.broker.test.integration - -import com.hivemq.client.mqtt.datatypes.MqttQos -import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient -import com.hivemq.client.mqtt.mqtt3.message.Mqtt3MessageType -import com.hivemq.client.mqtt.mqtt3.message.publish.Mqtt3Publish -import com.hivemq.client.mqtt.mqtt3.message.subscribe.suback.Mqtt3SubAckReturnCode -import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient -import com.hivemq.client.mqtt.mqtt5.message.Mqtt5MessageType -import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5PayloadFormatIndicator -import com.hivemq.client.mqtt.mqtt5.message.publish.Mqtt5Publish -import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode - -import java.util.concurrent.atomic.AtomicReference - -class ConnectSubscribePublishTest extends IntegrationSpecification { - - def "publisher should publish message QoS 0 using mqtt 3.1.1"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt311Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt311Client() - when: - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_0) - subscribeResult.type == Mqtt3MessageType.SUBACK - - publishResult != null - publishResult.qos == MqttQos.AT_MOST_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.AT_MOST_ONCE - received.get().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "publisher should publish message QoS 0 using mqtt 5"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt5Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt5Client() - when: - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_MOST_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.AT_MOST_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_0) - subscribeResult.type == Mqtt5MessageType.SUBACK - - publishResult != null - publishResult.publish.qos == MqttQos.AT_MOST_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.AT_MOST_ONCE - received.get().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "publisher should publish message QoS 1 using mqtt 3.1.1"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt311Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt311Client() - when: - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_1) - subscribeResult.type == Mqtt3MessageType.SUBACK - - publishResult != null - publishResult.qos == MqttQos.AT_LEAST_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.AT_LEAST_ONCE - received.get().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "publisher should publish message QoS 1 using mqtt 5"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt5Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt5Client() - when: - - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.AT_LEAST_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.AT_LEAST_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_1) - subscribeResult.type == Mqtt5MessageType.SUBACK - - publishResult != null - publishResult.publish.qos == MqttQos.AT_LEAST_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.AT_LEAST_ONCE - received.get().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "publisher should publish message QoS 2 using mqtt 3.1.1"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt311Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt311Client() - when: - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.EXACTLY_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.EXACTLY_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.returnCodes.contains(Mqtt3SubAckReturnCode.SUCCESS_MAXIMUM_QOS_2) - subscribeResult.type == Mqtt3MessageType.SUBACK - - publishResult != null - publishResult.qos == MqttQos.EXACTLY_ONCE - publishResult.type == Mqtt3MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.EXACTLY_ONCE - received.get().type == Mqtt3MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def "publisher should publish message QoS 2 using mqtt 5"() { - given: - def received = new AtomicReference() - def subscriber = buildExternalMqtt5Client() - def subscriberId = subscriber.getConfig().clientIdentifier.get().toString() - def publisher = buildExternalMqtt5Client() - when: - - subscriber.connect().join() - publisher.connect().join() - - def subscribeResult = subscribe(subscriber, subscriberId, MqttQos.EXACTLY_ONCE, received) - def publishResult = publish(publisher, subscriberId, MqttQos.EXACTLY_ONCE) - - Thread.sleep(100) - then: - noExceptionThrown() - - subscribeResult != null - subscribeResult.reasonCodes.contains(Mqtt5SubAckReasonCode.GRANTED_QOS_2) - subscribeResult.type == Mqtt5MessageType.SUBACK - - publishResult != null - publishResult.publish.qos == MqttQos.EXACTLY_ONCE - publishResult.publish.type == Mqtt5MessageType.PUBLISH - - received.get() != null - received.get().qos == MqttQos.EXACTLY_ONCE - received.get().type == Mqtt5MessageType.PUBLISH - cleanup: - subscriber.disconnect().join() - publisher.disconnect().join() - } - - def publish(Mqtt5AsyncClient publisher, String subscriberId, MqttQos qos) { - return publisher.publishWith() - .topic("test/$subscriberId") - .qos(qos) - .payload(publishPayload) - .payloadFormatIndicator(Mqtt5PayloadFormatIndicator.UTF_8) - .send() - .join() - } - - def subscribe( - Mqtt5AsyncClient subscriber, - String subscriberId, - MqttQos qos, - AtomicReference received - ) { - return subscriber.subscribeWith() - .topicFilter("test/$subscriberId") - .qos(qos) - .callback({ publish -> received.set(publish) }) - .send() - .join() - } - - def publish(Mqtt3AsyncClient publisher, String subscriberId, MqttQos qos) { - return publisher.publishWith() - .topic("test/$subscriberId") - .qos(qos) - .payload(publishPayload) - .send() - .join() - } - - def subscribe( - Mqtt3AsyncClient subscriber, - String subscriberId, - MqttQos qos, - AtomicReference received - ) { - return subscriber.subscribeWith() - .topicFilter("test/$subscriberId") - .qos(qos) - .callback({ publish -> received.set(publish) }) - .send() - .join() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/ExternalConnectionTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/ExternalConnectionTest.groovy deleted file mode 100644 index 61a7729b..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/ExternalConnectionTest.groovy +++ /dev/null @@ -1,179 +0,0 @@ -package com.ss.mqtt.broker.test.integration - -import com.hivemq.client.mqtt.mqtt3.exceptions.Mqtt3ConnAckException -import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5ConnAckException -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import com.ss.mqtt.broker.network.packet.out.Connect311OutPacket -import javasabr.rlib.common.util.ArrayUtils -import spock.lang.Ignore - -import java.nio.charset.StandardCharsets -import java.util.concurrent.CompletionException - -class ExternalConnectionTest extends IntegrationSpecification { - - def "client should connect to broker without user and pass using mqtt 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - def result = client.connect().join() - then: - result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker without user and pass using mqtt 5"() { - given: - def client = buildExternalMqtt5Client() - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - result.sessionExpiryInterval.present - result.sessionExpiryInterval.getAsLong() == MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - result.serverKeepAlive.present - result.serverKeepAlive.getAsInt() == MqttPropertyConstants.SERVER_KEEP_ALIVE_DISABLED - !result.serverReference.present - !result.responseInformation.present - !result.assignedClientIdentifier.present - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker with user and pass using mqtt 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - def result = connectWith(client, 'user1', 'password') - then: - result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker with user and pass using mqtt 5"() { - given: - def client = buildExternalMqtt5Client() - when: - def result = connectWith(client, 'user1', 'password') - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - result.sessionExpiryInterval.present - result.sessionExpiryInterval.getAsLong() == MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - result.serverKeepAlive.present - result.serverKeepAlive.getAsInt() == MqttPropertyConstants.SERVER_KEEP_ALIVE_DISABLED - !result.serverReference.present - !result.responseInformation.present - !result.assignedClientIdentifier.present - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should not connect to broker without providing a client id using mqtt 3.1.1"() { - given: - def client = buildExternalMqtt311Client("") - when: - client.connect().join() - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED - } - - def "client should connect to broker without providing a client id using mqtt 5"() { - given: - def client = buildExternalMqtt5Client("") - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - result.assignedClientIdentifier.present - result.assignedClientIdentifier.get().toString() != "" - cleanup: - client.disconnect().join() - } - - def "client should not connect to broker with invalid client id using mqtt 3.1.1"(String clientId) { - given: - def client = buildExternalMqtt311Client(clientId) - when: - client.connect().join() - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.IDENTIFIER_REJECTED - where: - clientId << ["!@#!@*()^&"] - } - - def "client should not connect to broker with invalid client id using mqtt 5"(String clientId) { - given: - def client = buildExternalMqtt5Client(clientId) - when: - client.connect().join() - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt5ConnAckException - cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.CLIENT_IDENTIFIER_NOT_VALID - where: - clientId << ["!@#!@*()^&"] - } - - def "client should not connect to broker with wrong pass using mqtt 3.1.1"() { - given: - def client = buildExternalMqtt311Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt3ConnAckException - cause.mqttMessage.returnCode == Mqtt3ConnAckReturnCode.BAD_USER_NAME_OR_PASSWORD - } - - @Ignore - def "client should not connect to broker without username and with pass using mqtt 3.1.1"() { - given: - def client = buildMqtt311MockClient() - def clientId = generateClientId() - when: - - client.connect() - client.send(new Connect311OutPacket( - "", - "", - clientId, - "wrongPassword".getBytes(StandardCharsets.UTF_8), - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - false, - false - )) - - def connectAck = client.readNext() as ConnectAckInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } - - def "client should not connect to broker with wrong pass using mqtt 5"() { - given: - def client = buildExternalMqtt5Client() - when: - connectWith(client, "user", "wrongPassword") - then: - def ex = thrown CompletionException - def cause = ex.cause as Mqtt5ConnAckException - cause.mqttMessage.reasonCode == Mqtt5ConnAckReasonCode.BAD_USER_NAME_OR_PASSWORD - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/IntegrationSpecification.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/IntegrationSpecification.groovy deleted file mode 100644 index 470d3b5e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/IntegrationSpecification.groovy +++ /dev/null @@ -1,174 +0,0 @@ -package com.ss.mqtt.broker.test.integration - -import com.hivemq.client.mqtt.MqttClient -import com.hivemq.client.mqtt.mqtt3.Mqtt3AsyncClient -import com.hivemq.client.mqtt.mqtt5.Mqtt5AsyncClient -import com.ss.mqtt.broker.config.MqttConnectionConfig -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.MqttVersion -import com.ss.mqtt.broker.network.MqttConnection -import com.ss.mqtt.broker.test.integration.config.MqttBrokerTestConfig -import com.ss.mqtt.broker.test.mock.MqttMockClient -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.junit.jupiter.SpringJUnitConfig -import spock.lang.Specification - -import java.nio.charset.StandardCharsets -import java.util.concurrent.atomic.AtomicInteger - -@SpringJUnitConfig(classes = MqttBrokerTestConfig) -class IntegrationSpecification extends Specification { - - public static final encoding = StandardCharsets.UTF_8 - public static final topicFilter = "topic/Filter" - public static final publishPayload = "publishPayload".getBytes(encoding) - public static final clientId = "testClientId" - public static final keepAlive = 120 - - private static final idGenerator = new AtomicInteger(1) - - @Autowired - InetSocketAddress externalNetworkAddress - - @Autowired - InetSocketAddress internalNetworkAddress - - @Autowired - MqttConnectionConfig externalConnectionConfig - - def buildExternalMqtt311Client() { - return buildMqtt311Client(generateClientId(), externalNetworkAddress) - } - - def buildInternalMqtt311Client() { - return buildMqtt311Client(generateClientId(), internalNetworkAddress) - } - - def buildExternalMqtt5Client() { - return buildMqtt5Client(generateClientId(), externalNetworkAddress) - } - - def buildInternalMqtt5Client() { - return buildMqtt5Client(generateClientId(), internalNetworkAddress) - } - - def buildExternalMqtt311Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(externalNetworkAddress.getHostName()) - .serverPort(externalNetworkAddress.getPort()) - .useMqttVersion3() - .build() - .toAsync() - } - - def buildMqtt311Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion3() - .build() - .toAsync() - } - - def buildExternalMqtt5Client(String clientId) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(externalNetworkAddress.getHostName()) - .serverPort(externalNetworkAddress.getPort()) - .useMqttVersion5() - .build() - .toAsync() - } - - def buildMqtt5Client(String clientId, InetSocketAddress address) { - return MqttClient.builder() - .identifier(clientId) - .serverHost(address.getHostName()) - .serverPort(address.getPort()) - .useMqttVersion5() - .build() - .toAsync() - } - - def generateClientId() { - return generateClientId("Default") - } - - def generateClientId(String prefix) { - return prefix + "_" + idGenerator.incrementAndGet() - } - - def connectWith(Mqtt3AsyncClient client, String user, String pass) { - return client.connectWith() - .simpleAuth() - .username(user) - .password(pass.getBytes(encoding)) - .applySimpleAuth() - .send() - .join() - } - - def connectWith(Mqtt5AsyncClient client, String user, String pass) { - return client.connectWith() - .simpleAuth() - .username(user) - .password(pass.getBytes(encoding)) - .applySimpleAuth() - .send() - .join() - } - - def buildMqtt5MockClient() { - return new MqttMockClient( - externalNetworkAddress.getHostName(), - externalNetworkAddress.getPort(), - mqtt5MockedConnection(externalConnectionConfig) - ) - } - - def buildMqtt311MockClient() { - return new MqttMockClient( - externalNetworkAddress.getHostName(), - externalNetworkAddress.getPort(), - mqtt311MockedConnection(externalConnectionConfig) - ) - } - - def mqtt5MockedConnection(MqttConnectionConfig deviceConnectionConfig) { - - return Stub(MqttConnection) { - isSupported(MqttVersion.MQTT_5) >> true - isSupported(MqttVersion.MQTT_3_1_1) >> true - getConfig() >> deviceConnectionConfig - getClient() >> Stub(com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient) { - getConnectionConfig() >> deviceConnectionConfig - getSessionExpiryInterval() >> MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DISABLED - getReceiveMax() >> deviceConnectionConfig.getReceiveMaximum() - getMaximumPacketSize() >> deviceConnectionConfig.getMaximumPacketSize() - getClientId() >> IntegrationSpecification.clientId - getKeepAlive() >> MqttPropertyConstants.SERVER_KEEP_ALIVE_DEFAULT - getTopicAliasMaximum() >> deviceConnectionConfig.getTopicAliasMaximum() - } - } - } - - def mqtt311MockedConnection(MqttConnectionConfig deviceConnectionConfig) { - return Stub(MqttConnection) { - isSupported(MqttVersion.MQTT_5) >> false - isSupported(MqttVersion.MQTT_3_1_1) >> true - getConfig() >> deviceConnectionConfig - getClient() >> Stub(com.ss.mqtt.broker.network.client.MqttClient.UnsafeMqttClient) { - getConnectionConfig() >> deviceConnectionConfig - getSessionExpiryInterval() >> MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DISABLED - getReceiveMax() >> deviceConnectionConfig.getReceiveMaximum() - getMaximumPacketSize() >> deviceConnectionConfig.getMaximumPacketSize() - getClientId() >> IntegrationSpecification.clientId - getKeepAlive() >> MqttPropertyConstants.SERVER_KEEP_ALIVE_DEFAULT - getTopicAliasMaximum() >> deviceConnectionConfig.getTopicAliasMaximum() - } - } - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/InternalConnectionTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/InternalConnectionTest.groovy deleted file mode 100644 index afc09955..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/InternalConnectionTest.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package com.ss.mqtt.broker.test.integration - -import com.hivemq.client.mqtt.mqtt3.message.connect.connack.Mqtt3ConnAckReturnCode -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import com.ss.mqtt.broker.model.MqttPropertyConstants - -class InternalConnectionTest extends IntegrationSpecification { - - def "client should connect to broker without user and pass using mqtt 3.1.1"() { - given: - def client = buildInternalMqtt311Client() - when: - def result = client.connect().join() - then: - result.returnCode == Mqtt3ConnAckReturnCode.SUCCESS - !result.sessionPresent - cleanup: - client.disconnect().join() - } - - def "client should connect to broker without user and pass using mqtt 5"() { - given: - def client = buildInternalMqtt5Client() - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - result.sessionExpiryInterval.present - result.sessionExpiryInterval.getAsLong() == MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_DEFAULT - result.serverKeepAlive.present - result.serverKeepAlive.getAsInt() == MqttPropertyConstants.SERVER_KEEP_ALIVE_DISABLED - !result.serverReference.present - !result.responseInformation.present - !result.assignedClientIdentifier.present - !result.sessionPresent - cleanup: - client.disconnect().join() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/PublishRetryTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/PublishRetryTest.groovy deleted file mode 100644 index 1d025d0e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/PublishRetryTest.groovy +++ /dev/null @@ -1,323 +0,0 @@ -package com.ss.mqtt.broker.test.integration - -import com.hivemq.client.mqtt.datatypes.MqttQos -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.SubscribeTopicFilter -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import com.ss.mqtt.broker.network.packet.in.PublishInPacket -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.Connect311OutPacket -import com.ss.mqtt.broker.network.packet.out.Connect5OutPacket -import com.ss.mqtt.broker.network.packet.out.PublishComplete311OutPacket -import com.ss.mqtt.broker.network.packet.out.PublishComplete5OutPacket -import com.ss.mqtt.broker.network.packet.out.PublishReceived311OutPacket -import com.ss.mqtt.broker.network.packet.out.PublishReceived5OutPacket -import com.ss.mqtt.broker.network.packet.out.Subscribe311OutPacket -import com.ss.mqtt.broker.network.packet.out.Subscribe5OutPacket -import com.ss.mqtt.broker.service.MqttSessionService -import javasabr.rlib.collections.array.Array -import org.springframework.beans.factory.annotation.Autowired - -class PublishRetryTest extends IntegrationSpecification { - - @Autowired - MqttSessionService mqttSessionService - - def "mqtt 3.1.1 client should be generate session with one pending QoS 1 packet"() { - given: - def publisher = buildExternalMqtt5Client() - def subscriber = buildMqtt311MockClient() - def subscriberId = generateClientId() - when: - - publisher.connect().join() - - subscriber.connect() - subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) - - def connectAck = subscriber.readNext() as ConnectAckInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - when: - - subscriber.send(new Subscribe311OutPacket( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), - 1 - )) - - def subscribeAck = subscriber.readNext() as SubscribeAckInPacket - - then: - subscribeAck.reasonCodes.stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) - when: - - publisher.publishWith() - .topic("test/retry/$subscriberId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - - def receivedPublish = subscriber.readNext() as PublishInPacket - - then: - receivedPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - def receivedDupPublish = subscriber.readNext() as PublishInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 5 client should be generate session with one pending QoS 1 packet"() { - given: - def publisher = buildExternalMqtt5Client() - def subscriber = buildMqtt5MockClient() - def subscriberId = generateClientId() - when: - - publisher.connect().join() - - subscriber.connect() - subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) - - def connectAck = subscriber.readNext() as ConnectAckInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - when: - - subscriber.send(new Subscribe5OutPacket( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.AT_LEAST_ONCE)), - 1 - )) - - def subscribeAck = subscriber.readNext() as SubscribeAckInPacket - - then: - subscribeAck.reasonCodes.stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_1 }) - when: - - publisher.publishWith() - .topic("test/retry/$subscriberId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - - def receivedPublish = subscriber.readNext() as PublishInPacket - - then: - receivedPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - def receivedDupPublish = subscriber.readNext() as PublishInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 3.1.1 client should be generate session with one pending QoS 2 packet"() { - given: - def publisher = buildExternalMqtt5Client() - def subscriber = buildMqtt311MockClient() - def subscriberId = generateClientId() - when: - - publisher.connect().join() - - subscriber.connect() - subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) - - def connectAck = subscriber.readNext() as ConnectAckInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - when: - - subscriber.send(new Subscribe311OutPacket( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), - 1 - )) - - def subscribeAck = subscriber.readNext() as SubscribeAckInPacket - - then: - subscribeAck.reasonCodes.stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) - when: - - publisher.publishWith() - .topic("test/retry/$subscriberId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - - def receivedPublish = subscriber.readNext() as PublishInPacket - - then: - receivedPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - def receivedDupPublish = subscriber.readNext() as PublishInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect311OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - receivedDupPublish = subscriber.readNext() as PublishInPacket - - subscriber.send(new PublishReceived311OutPacket(receivedDupPublish.getPacketId())) - def releaseAck = subscriber.readNext() as PublishReleaseInPacket - - subscriber.send(new PublishComplete311OutPacket(receivedDupPublish.getPacketId())) - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - releaseAck.packetId == receivedPublish.packetId - cleanup: - subscriber.close() - publisher.disconnect().join() - } - - def "mqtt 5 client should be generate session with one pending QoS 2 packet"() { - given: - def publisher = buildExternalMqtt5Client() - def subscriber = buildMqtt5MockClient() - def subscriberId = generateClientId() - when: - - publisher.connect().join() - - subscriber.connect() - subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) - - def connectAck = subscriber.readNext() as ConnectAckInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - when: - - subscriber.send(new Subscribe5OutPacket( - Array.of(new SubscribeTopicFilter("test/retry/$subscriberId", QoS.EXACTLY_ONCE)), - 1 - )) - - def subscribeAck = subscriber.readNext() as SubscribeAckInPacket - - then: - subscribeAck.reasonCodes.stream() - .allMatch({ it == SubscribeAckReasonCode.GRANTED_QOS_2 }) - when: - - publisher.publishWith() - .topic("test/retry/$subscriberId") - .qos(MqttQos.AT_MOST_ONCE) - .payload(publishPayload) - .send() - .join() - - def receivedPublish = subscriber.readNext() as PublishInPacket - - then: - receivedPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - def receivedDupPublish = subscriber.readNext() as PublishInPacket - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - when: - - subscriber.close() - - subscriber.connect() - subscriber.send(new Connect5OutPacket(subscriberId, keepAlive)) - - connectAck = subscriber.readNext() as ConnectAckInPacket - receivedDupPublish = subscriber.readNext() as PublishInPacket - - subscriber.send(new PublishReceived5OutPacket( - receivedDupPublish.getPacketId(), - PublishReceivedReasonCode.SUCCESS - )) - - def releaseAck = subscriber.readNext() as PublishReleaseInPacket - - subscriber.send(new PublishComplete5OutPacket( - receivedDupPublish.getPacketId(), - PublishCompletedReasonCode.SUCCESS - )) - - then: - connectAck.reasonCode == ConnectAckReasonCode.SUCCESS - receivedDupPublish.duplicate - receivedDupPublish.packetId == receivedPublish.packetId - receivedDupPublish.payload == publishPayload - releaseAck.packetId == receivedPublish.packetId - cleanup: - subscriber.close() - publisher.disconnect().join() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/ClientIdRegistryTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/service/ClientIdRegistryTest.groovy deleted file mode 100644 index 9b790c0e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/ClientIdRegistryTest.groovy +++ /dev/null @@ -1,132 +0,0 @@ -package com.ss.mqtt.broker.test.integration.service - -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import com.ss.mqtt.broker.service.ClientIdRegistry -import com.ss.mqtt.broker.test.integration.IntegrationSpecification -import javasabr.rlib.common.util.StringUtils -import org.springframework.beans.factory.annotation.Autowired - -class ClientIdRegistryTest extends IntegrationSpecification { - - @Autowired - ClientIdRegistry clientIdRegistry - - def "should register new client ids"() { - - given: - def clientId1 = "testClientId1" - def clientId2 = "testClientId2" - when: - def result1 = clientIdRegistry.register(clientId1).block() - def result2 = clientIdRegistry.register(clientId2).block() - then: - result1 && result2 - cleanup: - clientIdRegistry.unregister(clientId1).block() - clientIdRegistry.unregister(clientId2).block() - } - - def "should not register duplicated client ids"() { - - given: - - def clientId1 = "testClientId3" - def clientId2 = "testClientId4" - - clientIdRegistry.register(clientId1).block() - clientIdRegistry.register(clientId2).block() - - when: - def result1 = clientIdRegistry.register(clientId1).block() - def result2 = clientIdRegistry.register(clientId2).block() - then: - !result1 && !result2 - cleanup: - clientIdRegistry.unregister(clientId1).block() - clientIdRegistry.unregister(clientId2).block() - } - - def "should unregister exist client ids"() { - - given: - - def clientId1 = "testClientId5" - def clientId2 = "testClientId6" - - clientIdRegistry.register(clientId1).block() - clientIdRegistry.register(clientId2).block() - - when: - def result1 = clientIdRegistry.unregister(clientId1).block() - def result2 = clientIdRegistry.unregister(clientId2).block() - then: - result1 && result2 - } - - def "should not unregister not exist client ids"() { - - given: - def clientId1 = "testClientId7" - def clientId2 = "testClientId8" - when: - def result1 = clientIdRegistry.unregister(clientId1).block() - def result2 = clientIdRegistry.unregister(clientId2).block() - then: - !result1 && !result2 - } - - def "should generate and register new client ids"() { - - given: - def clientId1 = clientIdRegistry.generate().block() - def clientId2 = clientIdRegistry.generate().block() - when: - def result1 = clientIdRegistry.register(clientId1).block() - def result2 = clientIdRegistry.register(clientId2).block() - then: - result1 && result2 - StringUtils.isNotEmpty(clientId1) - StringUtils.isNotEmpty(clientId2) - cleanup: - clientIdRegistry.unregister(clientId1).block() - clientIdRegistry.unregister(clientId2).block() - } - - def "should generate invalid client ids"() { - - given: - def clientId1 = "testClientId*^&%" - def clientId2 = "testClientId{}@!" - def clientId3 = "testClientId9" - when: - def result1 = clientIdRegistry.validate(clientId1) - def result2 = clientIdRegistry.validate(clientId2) - def result3 = clientIdRegistry.validate(clientId3) - then: - !result1 && !result2 && result3 - } - - def "subscriber should register its client id on connect and unregister on disconnect"() { - given: - def clientId = clientIdRegistry.generate().block() - def client = buildExternalMqtt5Client(clientId) - when: - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - !clientIdRegistry.register(clientId).block() - when: - client.disconnect().join() - Thread.sleep(100) - then: - clientIdRegistry.register(clientId).block() - clientIdRegistry.unregister(clientId).block() - when: - result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - !clientIdRegistry.register(clientId).block() - cleanup: - client.disconnect().join() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy deleted file mode 100644 index 168dfa97..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/DisabledFeaturesSubscribtionServiceTest.groovy +++ /dev/null @@ -1,49 +0,0 @@ -package com.ss.mqtt.broker.test.integration.service - -import com.hivemq.client.mqtt.MqttClientState -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5SubAckException -import com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode -import com.ss.mqtt.broker.test.integration.IntegrationSpecification -import org.springframework.test.context.TestPropertySource -import spock.lang.Unroll - -import java.util.concurrent.CompletionException - -import static com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode.SHARED_SUBSCRIPTIONS_NOT_SUPPORTED -import static com.hivemq.client.mqtt.mqtt5.message.subscribe.suback.Mqtt5SubAckReasonCode.WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED - -@TestPropertySource(locations = "classpath:disabled-features.properties") -class DisabledFeaturesSubscribtionServiceTest extends IntegrationSpecification { - - @Unroll - def "should reject subscribe with wrong topic filter"( - String wrongTopicFilter, - Mqtt5SubAckReasonCode reasonCode - ) { - given: - def subscriber = buildExternalMqtt5Client() - when: - subscriber.connectWith() - .send() - .join() - subscriber.subscribeWith() - .topicFilter(wrongTopicFilter) - .send() - .join() - then: - Thread.sleep(10) - subscriber.state == MqttClientState.DISCONNECTED - def ex = thrown CompletionException - if (ex.cause != null) { - ex.cause.class == Mqtt5SubAckException - ex.cause.message == "SUBACK contains only Error Codes" - ((Mqtt5SubAckException) ex.cause).mqttMessage.reasonCodes.contains(reasonCode) - } - where: - wrongTopicFilter | reasonCode - "topic/+" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED - "topic/#" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED - "topic/+/Filter" | WILDCARD_SUBSCRIPTIONS_NOT_SUPPORTED - "\$share/group/topic/Filter" | SHARED_SUBSCRIPTIONS_NOT_SUPPORTED - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/MqttSessionServiceTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/service/MqttSessionServiceTest.groovy deleted file mode 100644 index 950c73b0..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/MqttSessionServiceTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package com.ss.mqtt.broker.test.integration.service - -import com.hivemq.client.mqtt.mqtt5.message.connect.connack.Mqtt5ConnAckReasonCode -import com.ss.mqtt.broker.service.ClientIdRegistry -import com.ss.mqtt.broker.service.MqttSessionService -import com.ss.mqtt.broker.test.integration.IntegrationSpecification -import org.springframework.beans.factory.annotation.Autowired - -class MqttSessionServiceTest extends IntegrationSpecification { - - @Autowired - ClientIdRegistry clientIdRegistry - - @Autowired - MqttSessionService mqttSessionService - - def "subscriber should create and re-use mqtt session"() { - given: - def clientId = clientIdRegistry.generate().block() - def client = buildExternalMqtt5Client(clientId) - when: - def shouldNoSession = mqttSessionService.restore(clientId).block() - def result = client.connect().join() - then: - result.reasonCode == Mqtt5ConnAckReasonCode.SUCCESS - shouldNoSession == null - mqttSessionService.restore(clientId).block() == null - when: - client.disconnect().join() - Thread.sleep(100) - def restored = mqttSessionService.restore(clientId).block() - then: - restored != null - when: - mqttSessionService.store(clientId, restored, externalConnectionConfig.getDefaultSessionExpiryInterval()).block() - client.connect().join() - shouldNoSession = mqttSessionService.restore(clientId).block() - then: - shouldNoSession == null - when: - client.disconnect().join() - Thread.sleep(100) - restored = mqttSessionService.restore(clientId).block() - then: - restored != null - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/SubscribtionServiceTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/integration/service/SubscribtionServiceTest.groovy deleted file mode 100644 index dc5fc360..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/integration/service/SubscribtionServiceTest.groovy +++ /dev/null @@ -1,208 +0,0 @@ -package com.ss.mqtt.broker.test.integration.service - - -import com.hivemq.client.mqtt.datatypes.MqttQos -import com.hivemq.client.mqtt.mqtt5.exceptions.Mqtt5SubAckException -import com.ss.mqtt.broker.model.SingleSubscriber -import com.ss.mqtt.broker.service.ClientIdRegistry -import com.ss.mqtt.broker.service.impl.SimpleSubscriptionService -import com.ss.mqtt.broker.test.integration.IntegrationSpecification -import org.spockframework.util.Pair -import org.springframework.beans.factory.annotation.Autowired -import spock.lang.Unroll - -import java.util.concurrent.CompletionException - -import static com.hivemq.client.mqtt.datatypes.MqttQos.* -import static com.ss.mqtt.broker.model.ActionResult.SUCCESS -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicName -import static org.spockframework.util.Pair.of - -class SubscribtionServiceTest extends IntegrationSpecification { - - @Autowired - ClientIdRegistry clientIdRegistry - - @Autowired - SimpleSubscriptionService subscriptionService - - def "should clear/restore topic subscribers after disconnect/reconnect"() { - given: - def subscriber = buildExternalMqtt5Client(clientId) - def topicName = buildTopicName(topicFilter) - - def matchesCount = 0 - SingleSubscriber matchedSubscriber = null - def action = { subs, empty -> - matchesCount++ - matchedSubscriber = subs - SUCCESS - } - when: - subscriber.connectWith() - .cleanStart(true) - .send() - .join() - subscriber.subscribeWith() - .topicFilter(topicFilter) - .qos(AT_MOST_ONCE) - .send() - .join() - - def actionResult = subscriptionService.forEachTopicSubscriber(topicName, null, action) - then: - matchesCount == 1 - matchedSubscriber.mqttClient.getClientId() == clientId - matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter - actionResult == SUCCESS - when: - subscriber.disconnect().join() - subscriber.connectWith() - .cleanStart(false) - .send() - .join() - - actionResult = subscriptionService.forEachTopicSubscriber(topicName, clientId, action) - then: - matchesCount == 2 - matchedSubscriber.mqttClient.getClientId() == clientId - matchedSubscriber.subscribe.topicFilter.getRawTopic() == topicFilter - actionResult == SUCCESS - cleanup: - subscriber.disconnect().join() - } - - @Unroll - def "should match subscriber with the highest QoS"( - String topicName, - Pair topicFilter1, - Pair topicFilter2, - String targetTopicFilter - ) { - given: - def subscriber = buildExternalMqtt5Client() - - def matchesCount = 0 - SingleSubscriber matchedSubscriber = null - def action = { subs, empty -> - matchesCount++ - matchedSubscriber = subs - SUCCESS - } - subscriber.connectWith() - .send() - .join() - subscriber.subscribeWith() - .topicFilter(topicFilter1.first()) - .qos(topicFilter1.second()) - .send() - .join() - subscriber.subscribeWith() - .topicFilter(topicFilter2.first()) - .qos(topicFilter2.second()) - .send() - .join() - when: - subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), null, action) - then: - matchesCount == 1 - matchedSubscriber.subscribe.topicFilter.getRawTopic() == targetTopicFilter - cleanup: - subscriber.disconnect().join() - where: - topicName | topicFilter1 | topicFilter2 | targetTopicFilter - "topic/Filter" | of("topic/Filter", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" - "topic/Another" | of("topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/#" - "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" - } - - @Unroll - def "should match all subscribers with shared and single topic"( - String topicName, - Pair topicFilter1, - Pair topicFilter2, - String targetTopicFilter, - int targetCount - ) { - given: - def clientId1 = clientIdRegistry.generate().block() - def clientId2 = clientIdRegistry.generate().block() - def subscriber1 = buildExternalMqtt5Client(clientId1) - def subscriber2 = buildExternalMqtt5Client(clientId2) - - def matchesCount = 0 - def matchedSubscribers = new LinkedHashSet() - def action = { SingleSubscriber subscriber, String clientId -> - matchesCount++ - matchedSubscribers.add(subscriber.mqttClient.clientId) - SUCCESS - } - - subscriber1.connectWith() - .send() - .join() - subscriber2.connectWith() - .send() - .join() - - subscriber1.subscribeWith() - .topicFilter(topicFilter1.first()) - .qos(topicFilter1.second()) - .send() - .join() - subscriber2.subscribeWith() - .topicFilter(topicFilter2.first()) - .qos(topicFilter2.second()) - .send() - .join() - when: - subscriptionService.forEachTopicSubscriber(buildTopicName(topicName), clientId, action) - then: - matchesCount == targetCount - matchedSubscribers[0] == clientId1 - matchedSubscribers[1] == clientId2 - cleanup: - subscriber1.disconnect().join() - subscriber2.disconnect().join() - where: - topicName | topicFilter1 | topicFilter2 | targetTopicFilter | targetCount - "topic/Filter" | of("\$share/group1/topic/Filter", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 - "topic/Filter" | of("\$share/group1/topic/Filter", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/Filter" | 2 - "topic/Filter/First" | of("topic/+/First", AT_MOST_ONCE) | of("\$share/group2/topic/#", AT_LEAST_ONCE) | "topic/#" | 2 - "topic/Filter/First" | of("topic/+/First", EXACTLY_ONCE) | of("topic/#", AT_LEAST_ONCE) | "topic/+/First" | 2 - } - - @Unroll - def "should reject subscribe with wrong topic filter"( - String wrongTopicFilter, - Class exception - ) { - given: - def subscriber = buildExternalMqtt5Client() - when: - subscriber.connectWith() - .send() - .join() - subscriber.subscribeWith() - .topicFilter(wrongTopicFilter) - .send() - .join() - then: - def ex = thrown exception - if (ex.cause != null) { - ex.cause.class == Mqtt5SubAckException - ex.cause.message == "SUBACK contains only Error Codes" - } - cleanup: - subscriber.disconnect().join() - where: - wrongTopicFilter | exception - "topic/" | CompletionException - "topic//Filter" | CompletionException - "/topic/Another" | CompletionException - "topic/##" | IllegalArgumentException - "++/Filter/First" | IllegalArgumentException - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/mock/MqttMockClient.groovy b/src/test/groovy/com/ss/mqtt/broker/test/mock/MqttMockClient.groovy deleted file mode 100644 index 3ada639a..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/mock/MqttMockClient.groovy +++ /dev/null @@ -1,124 +0,0 @@ -package com.ss.mqtt.broker.test.mock - -import com.ss.mqtt.broker.network.MqttConnection -import com.ss.mqtt.broker.network.packet.PacketType -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import com.ss.mqtt.broker.network.packet.in.MqttReadablePacket -import com.ss.mqtt.broker.network.packet.in.PublishInPacket -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.MqttWritablePacket -import com.ss.mqtt.broker.util.MqttDataUtils -import javasabr.rlib.common.util.NumberUtils - -import java.nio.ByteBuffer - -class MqttMockClient { - - private final ByteBuffer received = ByteBuffer.allocate(1024).clear() - - private final String brokerHost - private final int brokerPort - private final MqttConnection connection - - private Socket socket - - MqttMockClient(String brokerHost, int brokerPort, MqttConnection connection) { - this.brokerHost = brokerHost - this.brokerPort = brokerPort - this.connection = connection - } - - void connect() { - - if (socket != null) { - return - } - - socket = new Socket(brokerHost, brokerPort) - } - - void send(MqttWritablePacket packet) { - - def dataBuffer = ByteBuffer.allocate(1024) - - packet.write(dataBuffer) - - dataBuffer.flip() - - def finalBuffer = ByteBuffer.allocate(1024) - finalBuffer.put((byte) packet.getPacketTypeAndFlags()) - - MqttDataUtils.writeMbi(dataBuffer.remaining(), finalBuffer) - - finalBuffer.put(dataBuffer).flip() - - def out = socket.getOutputStream() - out.write(finalBuffer.array(), 0, finalBuffer.remaining()) - - Thread.sleep(50) - } - - MqttReadablePacket readNext() { - - if (received.position() == 0) { - - def input = socket.getInputStream() - def readBytes = input.read(received.array(), received.position(), received.capacity() - received.position()) - - if (readBytes > 0) { - received.position(received.position() + readBytes) - } - } - - received.flip() - - if (!received.hasRemaining()) { - throw new IllegalStateException("No received bytes.") - } - - def startByte = Byte.toUnsignedInt(received.get()) - def type = NumberUtils.getHighByteBits(startByte) - def info = NumberUtils.getLowByteBits(startByte) - def dataSize = MqttDataUtils.readMbi(received) - - MqttReadablePacket packet - - switch (PacketType.fromByte(type)) { - case PacketType.CONNECT_ACK: - packet = new ConnectAckInPacket(info) - break - case PacketType.SUBSCRIBE_ACK: - packet = new SubscribeAckInPacket(info) - break - case PacketType.PUBLISH: - packet = new PublishInPacket(info) - break - case PacketType.PUBLISH_RELEASED: - packet = new PublishReleaseInPacket(info) - break - default: - throw new IllegalStateException("Unknown packet of type: $type") - } - - packet.read(connection, received, dataSize) - - if (received.hasRemaining()) { - received.compact() - } else { - received.clear() - } - - return packet - } - - def close() { - - if (socket != null) { - socket.close() - socket = null - received.clear() - Thread.sleep(50) - } - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/model/ActionResultTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/model/ActionResultTest.groovy deleted file mode 100644 index f3077f27..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/model/ActionResultTest.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.ss.mqtt.broker.test.model - -import com.ss.mqtt.broker.model.ActionResult -import com.ss.mqtt.broker.test.network.NetworkUnitSpecification -import spock.lang.Unroll - -import static com.ss.mqtt.broker.model.ActionResult.* - -class ActionResultTest extends NetworkUnitSpecification { - - @Unroll - def "#first and #second == #result"( - ActionResult first, - ActionResult second, - ActionResult result - ) { - expect: - first.and(second) == result - where: - first | second | result - SUCCESS | SUCCESS | SUCCESS - SUCCESS | FAILED | FAILED - FAILED | SUCCESS | FAILED - FAILED | FAILED | FAILED - EMPTY | EMPTY | EMPTY - EMPTY | FAILED | FAILED - FAILED | EMPTY | FAILED - SUCCESS | EMPTY | SUCCESS - EMPTY | SUCCESS | SUCCESS - } - -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/model/TopicSubscriberTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/model/TopicSubscriberTest.groovy deleted file mode 100644 index 275f174b..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/model/TopicSubscriberTest.groovy +++ /dev/null @@ -1,75 +0,0 @@ -package com.ss.mqtt.broker.test.model - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.SubscribeTopicFilter -import com.ss.mqtt.broker.model.topic.TopicFilter -import com.ss.mqtt.broker.model.topic.TopicName -import com.ss.mqtt.broker.model.topic.TopicSubscribers -import com.ss.mqtt.broker.network.client.MqttClient -import com.ss.mqtt.broker.test.network.NetworkUnitSpecification -import spock.lang.Unroll - -import static com.ss.mqtt.broker.model.QoS.* -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicName - -class TopicSubscriberTest extends NetworkUnitSpecification { - - @Unroll - def "should choose #matchedQos from #subscriberQos"( - TopicFilter[] topicFilters, - TopicName topicNames, - QoS[] subscriberQos, - QoS[] matchedQos, - MqttClient[] mqttClients - ) { - given: - def subscribeTopicFilter = Mock(SubscribeTopicFilter) { - getQos() >>> subscriberQos - getTopicFilter() >>> topicFilters - } - def topicSubscriber = new TopicSubscribers() - when: - topicSubscriber.addSubscriber(mqttClients[0], subscribeTopicFilter) - topicSubscriber.addSubscriber(mqttClients[1], subscribeTopicFilter) - topicSubscriber.addSubscriber(mqttClients[2], subscribeTopicFilter) - then: - def subscribers = topicSubscriber.matches(topicNames) - subscribers.size() == matchedQos.size() - for (int i = 0; i < subscribers.size(); i++) { - subscribers[i].qos == matchedQos[i] - } - where: - topicFilters << [ - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")], - [buildTopicFilter("topic/+/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/first/in"), buildTopicFilter("topic/out")], - [buildTopicFilter("topic/second/in"), buildTopicFilter("topic/+/in"), buildTopicFilter("topic/#")] - ] - topicNames << [ - buildTopicName("topic/second/in"), - buildTopicName("topic/first/in"), - buildTopicName("topic/second/in"), - buildTopicName("topic/second/in") - ] - subscriberQos << [ - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] - ] - matchedQos << [ - [EXACTLY_ONCE], - [AT_MOST_ONCE], - [AT_LEAST_ONCE], - [AT_LEAST_ONCE, AT_MOST_ONCE, EXACTLY_ONCE] - ] - mqttClients << [ - [defaultMqttClient, defaultMqttClient, defaultMqttClient], - [defaultMqttClient, defaultMqttClient, defaultMqttClient], - [defaultMqttClient, defaultMqttClient, defaultMqttClient], - [defaultMqttClient(), defaultMqttClient(), defaultMqttClient()] - ] - } - -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/model/TopicTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/model/TopicTest.groovy deleted file mode 100644 index 91f3cd4e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/model/TopicTest.groovy +++ /dev/null @@ -1,111 +0,0 @@ -package com.ss.mqtt.broker.test.model - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.SubscribeTopicFilter -import com.ss.mqtt.broker.model.topic.TopicFilter -import com.ss.mqtt.broker.model.topic.TopicName -import com.ss.mqtt.broker.model.topic.TopicSubscribers -import com.ss.mqtt.broker.network.client.MqttClient -import com.ss.mqtt.broker.util.TopicUtils -import spock.lang.Specification -import spock.lang.Unroll - -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicName -import static com.ss.mqtt.broker.util.TopicUtils.isInvalid -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter - -class TopicTest extends Specification { - - @Unroll - def "should create topic name: [#stringTopicName]"() { - when: - def topicName = buildTopicName(stringTopicName) - then: - topicName.segments.size() == levelsCount - topicName.rawTopic == stringTopicName - topicName.length == stringTopicName.length() - where: - stringTopicName | levelsCount - "topic/second/in" | 3 - "topic/second" | 2 - } - - @Unroll - def "should fail create topic name: [#stringTopicName]"() { - expect: - isInvalid(buildTopicName(stringTopicName)) - where: - stringTopicName << [ - "", - "topic/+", - "topic/#" - ] - } - - @Unroll - def "should create topic filter: [#stringTopicFilter]"() { - when: - def topicFilter = buildTopicFilter(stringTopicFilter) - then: - topicFilter.segments.size() == levelsCount - topicFilter.rawTopic == stringTopicFilter - topicFilter.length == stringTopicFilter.length() - where: - stringTopicFilter | levelsCount - "topic/in" | 2 - "topic/+" | 2 - "topic/#" | 2 - "topic/+/in" | 3 - } - - @Unroll - def "should fail create topic filter: [#stringTopicFilter]"() { - expect: - isInvalid(buildTopicFilter(stringTopicFilter)) - where: - stringTopicFilter << [ - "", - "topic/in/", - "/topic/in", - "topic//in", - "topic/++/in", - "topic/#/in", - "topic/##" - ] - } - - @Unroll - def "should match topic filter: [#topicFilter] with topic name: [#topicName]"() { - expect: - def builtTopicName = buildTopicName(topicName) - def builtTopicFilter = buildTopicFilter(topicFilter) - def subscribers = new TopicSubscribers() - subscribers.addSubscriber(Mock(MqttClient), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) - subscribers.matches(builtTopicName) - where: - topicFilter | topicName - "topic/in" | "topic/in" - "topic/+" | "topic/in" - "topic/#" | "topic/in" - "topic/+/in" | "topic/m/in" - } - - @Unroll - def "should not match topic filter: [#topicFilter] with topic name: [#topicName]"() { - expect: - def builtTopicName = buildTopicName(topicName) - def builtTopicFilter = buildTopicFilter(topicFilter) - def subscribers = new TopicSubscribers() - subscribers.addSubscriber(Mock(MqttClient), new SubscribeTopicFilter(builtTopicFilter, QoS.AT_LEAST_ONCE)) - !subscribers.matches(builtTopicName) - where: - topicFilter | topicName - "topic/in" | "topic/m/in" - // "topic/in" | "topic/in/m" - // "topic/+" | "topic/m/in" - // "topic/+" | "topic/in/m" - "topic/#" | "topic" - "topic/+/in" | "topic/m/n" - "topic/+/in" | "topic/in" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/NetworkUnitSpecification.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/NetworkUnitSpecification.groovy deleted file mode 100644 index e80458bc..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/NetworkUnitSpecification.groovy +++ /dev/null @@ -1,236 +0,0 @@ -package com.ss.mqtt.broker.test.network - -import com.ss.mqtt.broker.config.MqttConnectionConfig -import com.ss.mqtt.broker.model.MqttVersion -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.SubscribeRetainHandling -import com.ss.mqtt.broker.model.SubscribeTopicFilter -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode -import com.ss.mqtt.broker.network.MqttConnection -import com.ss.mqtt.broker.network.client.MqttClient -import com.ss.mqtt.broker.test.UnitSpecification -import javasabr.rlib.collections.array.Array -import javasabr.rlib.collections.array.ArrayFactory -import javasabr.rlib.collections.array.IntArray -import javasabr.rlib.common.util.ArrayUtils -import spock.lang.Shared - -import java.nio.charset.StandardCharsets - -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicFilter -import static com.ss.mqtt.broker.util.TopicUtils.buildTopicName - -class NetworkUnitSpecification extends UnitSpecification { - - public static final keepAliveEnabled = true - public static final sessionsEnabled = true - public static final retainAvailable = true - public static final sharedSubscriptionAvailable = true - public static final wildcardSubscriptionAvailable = true - public static final subscriptionIdAvailable = true - - public static final maxQos = QoS.AT_MOST_ONCE - public static final sessionPresent = true - public static final cleanStart = false - public static final willRetain = false - public static final clientId = "testClientId" - public static final packetId = 1234 as short - public static final userName = "testUser" - public static final userPassword = "testPassword".getBytes(StandardCharsets.UTF_8) - public static final keepAlive = 120 - public static final sessionExpiryInterval = 300 - public static final messageExpiryInterval = 60 - public static final topicAlias = 252 - public static final receiveMaximum = 10 - public static final maximumPacketSize = 1024 - public static final topicAliasMaximum = 32 - public static final subscriptionId = 637 - public static final subscriptionId2 = 623 - public static final serverKeepAlive = 1200 - public static final requestResponseInformation = true - public static final requestProblemInformation = true - public static final responseInformation = "responseInformation" - public static final authMethod = "testAuthMethod" - public static final authData = "testAuthData".getBytes(StandardCharsets.UTF_8) - public static final reasonString = "reasonString" - public static final publishTopic = buildTopicName("publish/Topic") - public static final responseTopic = "response/Topic" - public static final topicFilter = "topic/Filter" - public static final topicFilter1Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter), QoS.AT_LEAST_ONCE) - public static final topicFilter1Obj5 = new SubscribeTopicFilter( - buildTopicFilter(topicFilter), - QoS.AT_LEAST_ONCE, - SubscribeRetainHandling.DO_NOT_SEND, - true, - false, - ) - public static final topicFilter2 = "topic/Filter2" - public static final topicFilter2Obj311 = new SubscribeTopicFilter(buildTopicFilter(topicFilter2), QoS.EXACTLY_ONCE) - public static final topicFilter2Obj5 = new SubscribeTopicFilter( - buildTopicFilter(topicFilter2), - QoS.EXACTLY_ONCE, - SubscribeRetainHandling.DO_NOT_SEND, - true, - false, - ) - public static final serverReference = "serverReference" - public static final contentType = "application/json" - public static final subscribeAckReasonCodes = Array.typed( - SubscribeAckReasonCode, - SubscribeAckReasonCode.GRANTED_QOS_1, - SubscribeAckReasonCode.GRANTED_QOS_0, - SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - ) - public static final unsubscribeAckReasonCodes = Array.typed( - UnsubscribeAckReasonCode, - UnsubscribeAckReasonCode.SUCCESS, - UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR, - UnsubscribeAckReasonCode.UNSPECIFIED_ERROR - ) - public static final userProperties = Array.typed( - StringPair, - new StringPair("key1", "val1"), - new StringPair("key2", "val2"), - new StringPair("key3", "val3"), - ) - public static final subscriptionIds = IntArray.of(subscriptionId, subscriptionId2) - public static final topicFilters = Array.of(topicFilter, topicFilter2) - public static final topicFiltersObj311 = Array.of(topicFilter1Obj311, topicFilter2Obj311) - public static final topicFiltersObj5 = Array.of(topicFilter1Obj5, topicFilter2Obj5) - public static final publishPayload = "publishPayload".getBytes(StandardCharsets.UTF_8) - public static final correlationData = "correlationData".getBytes(StandardCharsets.UTF_8) - - @Shared - MqttClient defaultMqttClient = defaultMqttClient() - - @Shared - MqttConnectionConfig mqttConnectionConfig = defaultMqttConnectionConfig() - - @Shared - MqttConnection mqtt5Connection = defaultMqttConnection(MqttVersion.MQTT_5) - - @Shared - MqttConnection mqtt311Connection = defaultMqttConnection(MqttVersion.MQTT_3_1_1) - - MqttClient defaultMqttClient() { - return mqttClient( - mqttConnectionConfig, - sessionExpiryInterval, - receiveMaximum, - maximumPacketSize, - clientId, - serverKeepAlive, - topicAliasMaximum - ) - } - - MqttConnectionConfig defaultMqttConnectionConfig() { - return mqttConnectionConfig( - maxQos, - maximumPacketSize, - serverKeepAlive, - receiveMaximum, - topicAliasMaximum, - sessionExpiryInterval, - keepAliveEnabled, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable - ) - } - - - MqttConnection defaultMqttConnection(MqttVersion mqttVersion) { - return mqttConnection( - mqttVersion, - mqttConnectionConfig, - sessionExpiryInterval, - receiveMaximum, - maximumPacketSize, - clientId, - serverKeepAlive, - topicAliasMaximum - ) - } - - - static MqttConnectionConfig mqttConnectionConfig( - QoS maxQos, - int maximumPacketSize, - int serverKeepAlive, - int receiveMaximum, - int topicAliasMaximum, - long sessionExpiryInterval, - boolean keepAliveEnabled, - boolean sessionsEnabled, - boolean retainAvailable, - boolean wildcardSubscriptionAvailable, - boolean subscriptionIdAvailable, - boolean sharedSubscriptionAvailable - - ) { - return new MqttConnectionConfig( - maxQos, - maximumPacketSize, - serverKeepAlive, - receiveMaximum, - topicAliasMaximum, - sessionExpiryInterval, - keepAliveEnabled, - sessionsEnabled, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable - ) - } - - MqttConnection mqttConnection( - MqttVersion mqttVersion, - MqttConnectionConfig mqttConnectionConfig, - long sessionExpiryInterval, - int receiveMaximum, - int maximumPacketSize, - String clientId, - int serverKeepAlive, - int topicAliasMaximum - ) { - return Stub(MqttConnection) { - isSupported(_ as MqttVersion) >> { MqttVersion version -> mqttVersion >= version } - getConfig() >> mqttConnectionConfig - getClient() >> mqttClient( - mqttConnectionConfig, - sessionExpiryInterval, - receiveMaximum, - maximumPacketSize, - clientId, - serverKeepAlive, - topicAliasMaximum - ) - } - } - - MqttClient mqttClient( - MqttConnectionConfig mqttConnectionConfig, - long sessionExpiryInterval, - int receiveMaximum, - int maximumPacketSize, - String clientId, - int serverKeepAlive, - int topicAliasMaximum - ) { - return Stub(MqttClient.UnsafeMqttClient) { - getConnectionConfig() >> mqttConnectionConfig - getSessionExpiryInterval() >> sessionExpiryInterval - getReceiveMax() >> receiveMaximum - getMaximumPacketSize() >> maximumPacketSize - getClientId() >> clientId - getKeepAlive() >> serverKeepAlive - getTopicAliasMaximum() >> topicAliasMaximum - } - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/AuthenticationInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/AuthenticationInPacketTest.groovy deleted file mode 100644 index df1d6a52..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/AuthenticationInPacketTest.groovy +++ /dev/null @@ -1,86 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.network.packet.in.AuthenticationInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class AuthenticationInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(AuthenticateReasonCode.SUCCESS.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new AuthenticationInPacket(0b1111_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reasonCode == AuthenticateReasonCode.SUCCESS - packet.authenticationMethod == authMethod - packet.authenticationData == authData - packet.reason == reasonString - packet.userProperties == userProperties - when: - - propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - } - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - packet = new AuthenticationInPacket(0b1111_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - packet.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION - packet.authenticationMethod == authMethod - packet.authenticationData == authData - packet.reason == reasonString - packet.userProperties == userProperties - when: - - propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - } - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(AuthenticateReasonCode.CONTINUE_AUTHENTICATION.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - packet = new AuthenticationInPacket(0b1111_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION - packet.authenticationMethod == authMethod - packet.authenticationData == authData - packet.reason == "" - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/BaseInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/BaseInPacketTest.groovy deleted file mode 100644 index ac35361f..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/BaseInPacketTest.groovy +++ /dev/null @@ -1,6 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.test.network.NetworkUnitSpecification - -class BaseInPacketTest extends NetworkUnitSpecification { -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectAckInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectAckInPacketTest.groovy deleted file mode 100644 index da164843..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectAckInPacketTest.groovy +++ /dev/null @@ -1,126 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils - -class ConnectAckInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putBoolean(sessionPresent) - it.put(ConnectAckReasonCode.NOT_AUTHORIZED.mqtt311) - } - - when: - def packet = new ConnectAckInPacket(0b0010_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reasonCode == ConnectAckReasonCode.NOT_AUTHORIZED - packet.sessionPresent == sessionPresent - packet.serverReference == "" - packet.reason == "" - packet.assignedClientId == "" - packet.authenticationData == ArrayUtils.EMPTY_BYTE_ARRAY - packet.authenticationMethod == "" - packet.maximumQos == QoS.EXACTLY_ONCE - packet.retainAvailable == MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT - packet.sharedSubscriptionAvailable == MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - packet.wildcardSubscriptionAvailable == MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - packet.subscriptionIdAvailable == MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - packet.responseInformation == "" - packet.maximumPacketSize == MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED - packet.serverKeepAlive == MqttPropertyConstants.SERVER_KEEP_ALIVE_UNDEFINED - packet.sessionExpiryInterval == MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED - packet.topicAliasMaximum == MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED - packet.receiveMax == MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) - it.putProperty(PacketProperty.ASSIGNED_CLIENT_IDENTIFIER, clientId) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maximumPacketSize) - it.putProperty(PacketProperty.MAXIMUM_QOS, QoS.AT_LEAST_ONCE.ordinal()) - it.putProperty(PacketProperty.RECEIVE_MAXIMUM, receiveMaximum) - it.putProperty(PacketProperty.RETAIN_AVAILABLE, retainAvailable) - it.putProperty(PacketProperty.RESPONSE_INFORMATION, responseInformation) - it.putProperty(PacketProperty.SERVER_KEEP_ALIVE, serverKeepAlive) - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) - it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) - it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaximum) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putBoolean(sessionPresent) - it.put(ConnectAckReasonCode.PAYLOAD_FORMAT_INVALID.mqtt5) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new ConnectAckInPacket(0b0010_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reasonCode == ConnectAckReasonCode.PAYLOAD_FORMAT_INVALID - packet.sessionPresent == sessionPresent - packet.serverReference == serverReference - packet.reason == reasonString - packet.assignedClientId == clientId - packet.authenticationData == authData - packet.authenticationMethod == authMethod - packet.maximumPacketSize == maximumPacketSize - packet.maximumQos == QoS.AT_LEAST_ONCE - packet.receiveMax == receiveMaximum - packet.retainAvailable == retainAvailable - packet.responseInformation == responseInformation - packet.serverKeepAlive == serverKeepAlive - packet.sessionExpiryInterval == sessionExpiryInterval - packet.sharedSubscriptionAvailable == sharedSubscriptionAvailable - packet.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable - packet.subscriptionIdAvailable == subscriptionIdAvailable - packet.topicAliasMaximum == topicAliasMaximum - - when: - - propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SHARED_SUBSCRIPTION_AVAILABLE, sharedSubscriptionAvailable) - it.putProperty(PacketProperty.WILDCARD_SUBSCRIPTION_AVAILABLE, wildcardSubscriptionAvailable) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER_AVAILABLE, subscriptionIdAvailable) - } - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putBoolean(sessionPresent) - it.put(ConnectAckReasonCode.PACKET_TOO_LARGE.mqtt5) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - packet = new ConnectAckInPacket(0b0010_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reasonCode == ConnectAckReasonCode.PACKET_TOO_LARGE - packet.sharedSubscriptionAvailable == sharedSubscriptionAvailable - packet.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable - packet.subscriptionIdAvailable == subscriptionIdAvailable - - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectInPacketTest.groovy deleted file mode 100644 index 23d1eacb..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/ConnectInPacketTest.groovy +++ /dev/null @@ -1,120 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.exception.MalformedPacketMqttException -import com.ss.mqtt.broker.model.MqttVersion -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.network.packet.in.ConnectInPacket -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils - -class ConnectInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString("MQTT") - it.put(4 as byte) - it.put(0b11000010 as byte) - it.putShort(keepAlive as short) - it.putString(clientId) - it.putString(userName) - it.putBytes(userPassword) - } - - when: - def packet = new ConnectInPacket(0b0001_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.clientId == clientId - packet.mqttVersion == MqttVersion.MQTT_3_1_1 - packet.password == userPassword - packet.username == userName - packet.willTopic == "" - packet.willQos == 0 - packet.willPayload == ArrayUtils.EMPTY_BYTE_ARRAY - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.RECEIVE_MAXIMUM, receiveMaximum) - it.putProperty(PacketProperty.MAXIMUM_PACKET_SIZE, maximumPacketSize) - it.putProperty(PacketProperty.TOPIC_ALIAS_MAXIMUM, topicAliasMaximum) - it.putProperty(PacketProperty.REQUEST_RESPONSE_INFORMATION, requestResponseInformation ? 1 : 0) - it.putProperty(PacketProperty.REQUEST_PROBLEM_INFORMATION, requestProblemInformation ? 1 : 0) - it.putProperty(PacketProperty.AUTHENTICATION_METHOD, authMethod) - it.putProperty(PacketProperty.AUTHENTICATION_DATA, authData) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString("MQTT") - it.put(5 as byte) - it.put(0b11000010 as byte) - it.putShort(keepAlive as short) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.putString(clientId) - it.putString(userName) - it.putBytes(userPassword) - } - - when: - def packet = new ConnectInPacket(0b0001_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.keepAlive == keepAlive - packet.authenticationMethod == authMethod - packet.authenticationData == authData - packet.clientId == clientId - packet.mqttVersion == MqttVersion.MQTT_5 - packet.maximumPacketSize == maximumPacketSize - packet.password == userPassword - packet.username == userName - packet.topicAliasMaximum == topicAliasMaximum - packet.sessionExpiryInterval == sessionExpiryInterval - packet.receiveMax == receiveMaximum - packet.willTopic == "" - packet.willQos == 0 - packet.willPayload == ArrayUtils.EMPTY_BYTE_ARRAY - packet.userProperties == userProperties - } - - def "should not read packet correctly with invalid UTF8 strings"(byte[] stringBytes) { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString("MQTT") - it.put(5 as byte) - it.put(0b11000010 as byte) - it.putShort(keepAlive as short) - it.putMbi(0) - it.putBytes(stringBytes) - it.putString(userName) - it.putBytes(userPassword) - } - - when: - def packet = new ConnectInPacket(0b0001_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - !result - packet.exception instanceof MalformedPacketMqttException - where: - stringBytes << [ - // https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt - [0xF4, 0x90, 0x80, 0x80] as byte[], - [0xFE, 0xFE, 0xFF, 0xFF] as byte[], - [0xFC, 0x80, 0x80, 0x80, 0x80, 0xAF] as byte[], - [0xED, 0xAF, 0xBF, 0xED, 0xBF, 0xBF] as byte[], - ] - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/DisconnectInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/DisconnectInPacketTest.groovy deleted file mode 100644 index 544724b9..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/DisconnectInPacketTest.groovy +++ /dev/null @@ -1,61 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.network.packet.in.DisconnectInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class DisconnectInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(DisconnectReasonCode.QUOTA_EXCEEDED.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new DisconnectInPacket(0b1110_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.serverReference == serverReference - packet.reasonCode == DisconnectReasonCode.QUOTA_EXCEEDED - packet.sessionExpiryInterval == sessionExpiryInterval - packet.userProperties == userProperties - when: - - propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SESSION_EXPIRY_INTERVAL, sessionExpiryInterval) - it.putProperty(PacketProperty.SERVER_REFERENCE, serverReference) - } - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.put(DisconnectReasonCode.PACKET_TOO_LARGE.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - packet = new DisconnectInPacket(0b1110_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.serverReference == serverReference - packet.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE - packet.sessionExpiryInterval == sessionExpiryInterval - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishAckInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishAckInPacketTest.groovy deleted file mode 100644 index daced8c9..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishAckInPacketTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishAckInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - } - - when: - def packet = new PublishAckInPacket(0b0100_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishAckReasonCode.SUCCESS - packet.userProperties == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishAckReasonCode.PAYLOAD_FORMAT_INVALID.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new PublishAckInPacket(0b0100_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCode == PublishAckReasonCode.PAYLOAD_FORMAT_INVALID - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishAckReasonCode.UNSPECIFIED_ERROR.value) - it.putMbi(0) - } - - packet = new PublishAckInPacket(0b0100_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishCompleteInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishCompleteInPacketTest.groovy deleted file mode 100644 index dc44d74e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishCompleteInPacketTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishCompleteInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - } - - when: - def packet = new PublishCompleteInPacket(0b0111_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishCompletedReasonCode.SUCCESS - packet.userProperties == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new PublishCompleteInPacket(0b0111_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) - it.putMbi(0) - } - - packet = new PublishCompleteInPacket(0b0111_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishInPacketTest.groovy deleted file mode 100644 index e88c75ad..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishInPacketTest.groovy +++ /dev/null @@ -1,113 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.network.packet.in.PublishInPacket -import javasabr.rlib.collections.array.IntArray -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString(publishTopic.toString()) - it.putShort(packetId) - it.put(publishPayload) - } - - when: - def packet = new PublishInPacket(0b0110_0011 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == "" - packet.subscriptionIds == IntArray.empty() - packet.contentType == "" - packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY - packet.payload == publishPayload - packet.packetId == packetId - packet.userProperties == Array.empty() - packet.messageExpiryInterval == MqttPropertyConstants.MESSAGE_EXPIRY_INTERVAL_UNDEFINED - packet.topicAlias == MqttPropertyConstants.TOPIC_ALIAS_DEFAULT - packet.payloadFormatIndicator == MqttPropertyConstants.PAYLOAD_FORMAT_INDICATOR_DEFAULT - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.PAYLOAD_FORMAT_INDICATOR, 1) - it.putProperty(PacketProperty.MESSAGE_EXPIRY_INTERVAL, messageExpiryInterval) - it.putProperty(PacketProperty.TOPIC_ALIAS, topicAlias) - it.putProperty(PacketProperty.RESPONSE_TOPIC, responseTopic) - it.putProperty(PacketProperty.CORRELATION_DATA, correlationData) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionIds) - it.putProperty(PacketProperty.CONTENT_TYPE, contentType) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString(publishTopic.toString()) - it.putShort(packetId) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.put(publishPayload) - } - - when: - def packet = new PublishInPacket(0b0110_0011 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == responseTopic - packet.subscriptionIds == subscriptionIds - packet.contentType == contentType - packet.correlationData == correlationData - packet.payload == publishPayload - packet.packetId == packetId - packet.userProperties == userProperties - packet.messageExpiryInterval == messageExpiryInterval - packet.topicAlias == topicAlias - packet.payloadFormatIndicator - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putString(publishTopic.toString()) - it.putShort(packetId) - it.putMbi(0) - it.put(publishPayload) - } - - packet = new PublishInPacket(0b0110_0011 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.qos == QoS.AT_LEAST_ONCE - !packet.duplicate - packet.retained - packet.responseTopic == "" - packet.subscriptionIds == IntArray.empty() - packet.contentType == "" - packet.correlationData == ArrayUtils.EMPTY_BYTE_ARRAY - packet.payload == publishPayload - packet.packetId == packetId - packet.userProperties == Array.empty(StringPair) - packet.messageExpiryInterval == MqttPropertyConstants.MESSAGE_EXPIRY_INTERVAL_UNDEFINED - packet.topicAlias == MqttPropertyConstants.TOPIC_ALIAS_DEFAULT - packet.payloadFormatIndicator == MqttPropertyConstants.PAYLOAD_FORMAT_INDICATOR_DEFAULT - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReceivedInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReceivedInPacketTest.groovy deleted file mode 100644 index ae324aea..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReceivedInPacketTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishReceivedInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - } - - when: - def packet = new PublishReceivedInPacket(0b0101_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishReceivedReasonCode.SUCCESS - packet.userProperties == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishReceivedReasonCode.QUOTA_EXCEEDED.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new PublishReceivedInPacket(0b0101_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCode == PublishReceivedReasonCode.QUOTA_EXCEEDED - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) - it.putMbi(0) - } - - packet = new PublishReceivedInPacket(0b0101_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishReceivedReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReleaseInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReleaseInPacketTest.groovy deleted file mode 100644 index 12c8bbef..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/PublishReleaseInPacketTest.groovy +++ /dev/null @@ -1,72 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishReleaseInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - } - - when: - def packet = new PublishReleaseInPacket(0b0110_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishReleaseReasonCode.SUCCESS - packet.userProperties == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND.value) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - } - - when: - def packet = new PublishReleaseInPacket(0b0110_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCode == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(PublishReleaseReasonCode.SUCCESS.value) - it.putMbi(0) - } - - packet = new PublishReleaseInPacket(0b0110_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCode == PublishReleaseReasonCode.SUCCESS - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeAckInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeAckInPacketTest.groovy deleted file mode 100644 index 8eda8bfa..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeAckInPacketTest.groovy +++ /dev/null @@ -1,93 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.SubscribeAckReasonCode -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class SubscribeAckInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) - } - - when: - def packet = new SubscribeAckInPacket(0b1001_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 - packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.put(SubscribeAckReasonCode.GRANTED_QOS_0.value) - it.put(SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) - } - - when: - def packet = new SubscribeAckInPacket(0b1001_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.GRANTED_QOS_0 - packet.reasonCodes.get(1) == SubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(0) - it.put(SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_2.value) - it.put(SubscribeAckReasonCode.GRANTED_QOS_1.value) - it.put(SubscribeAckReasonCode.UNSPECIFIED_ERROR.value) - } - - packet = new SubscribeAckInPacket(0b1001_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == SubscribeAckReasonCode.SUBSCRIPTION_IDENTIFIERS_NOT_SUPPORTED - packet.reasonCodes.get(1) == SubscribeAckReasonCode.GRANTED_QOS_2 - packet.reasonCodes.get(2) == SubscribeAckReasonCode.GRANTED_QOS_1 - packet.reasonCodes.get(3) == SubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeInPacketTest.groovy deleted file mode 100644 index a0d4fc37..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/SubscribeInPacketTest.groovy +++ /dev/null @@ -1,114 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.model.SubscribeRetainHandling -import com.ss.mqtt.broker.network.packet.in.SubscribeInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class SubscribeInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putString(topicFilter) - it.put(0b0000_0001 as byte) - it.putString(topicFilter2) - it.put(0b0000_0010 as byte) - } - - when: - def packet = new SubscribeInPacket(0b1000_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - packet.topicFilters.get(0).isNoLocal() - packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - packet.topicFilters.get(1).isNoLocal() - packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND - packet.packetId == packetId - packet.userProperties == Array.empty() - packet.subscriptionId == MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.SUBSCRIPTION_IDENTIFIER, subscriptionId) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.putString(topicFilter) - it.put(0b0000_1001 as byte) - it.putString(topicFilter2) - it.put(0b0001_0110 as byte) - } - - when: - def packet = new SubscribeInPacket(0b0110_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - !packet.topicFilters.get(0).isNoLocal() - packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - packet.topicFilters.get(1).isNoLocal() - !packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND_IF_SUBSCRIPTION_DOES_NOT_EXIST - packet.packetId == packetId - packet.userProperties == userProperties - packet.subscriptionId == subscriptionId - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(0) - it.putString(topicFilter) - it.put(0b0000_0001 as byte) - it.putString(topicFilter2) - it.put(0b0000_0010 as byte) - } - - packet = new SubscribeInPacket(0b0110_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).getQos() == QoS.AT_LEAST_ONCE - packet.topicFilters.get(0).getTopicFilter().toString() == topicFilter - !packet.topicFilters.get(0).isNoLocal() - !packet.topicFilters.get(0).isRetainAsPublished() - packet.topicFilters.get(0).getRetainHandling() == SubscribeRetainHandling.SEND - packet.topicFilters.get(1).getQos() == QoS.EXACTLY_ONCE - packet.topicFilters.get(1).getTopicFilter().toString() == topicFilter2 - !packet.topicFilters.get(1).isNoLocal() - !packet.topicFilters.get(1).isRetainAsPublished() - packet.topicFilters.get(1).getRetainHandling() == SubscribeRetainHandling.SEND - packet.packetId == packetId - packet.userProperties == Array.empty() - packet.subscriptionId == MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeAckInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeAckInPacketTest.groovy deleted file mode 100644 index f69ddbb4..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeAckInPacketTest.groovy +++ /dev/null @@ -1,81 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode -import com.ss.mqtt.broker.network.packet.in.UnsubscribeAckInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class UnsubscribeAckInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - } - - when: - def packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCodes == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.REASON_STRING, reasonString) - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.put(UnsubscribeAckReasonCode.SUCCESS.value) - it.put(UnsubscribeAckReasonCode.SUCCESS.value) - it.put(UnsubscribeAckReasonCode.NOT_AUTHORIZED.value) - it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR.value) - } - - when: - def packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == reasonString - packet.packetId == packetId - packet.reasonCodes.size() == 4 - packet.reasonCodes.get(0) == UnsubscribeAckReasonCode.SUCCESS - packet.reasonCodes.get(1) == UnsubscribeAckReasonCode.SUCCESS - packet.reasonCodes.get(2) == UnsubscribeAckReasonCode.NOT_AUTHORIZED - packet.reasonCodes.get(3) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(0) - it.put(UnsubscribeAckReasonCode.UNSPECIFIED_ERROR.value) - it.put(UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR.value) - } - - packet = new UnsubscribeAckInPacket(0b1011_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.reason == "" - packet.packetId == packetId - packet.reasonCodes.size() == 2 - packet.reasonCodes.get(0) == UnsubscribeAckReasonCode.UNSPECIFIED_ERROR - packet.reasonCodes.get(1) == UnsubscribeAckReasonCode.IMPLEMENTATION_SPECIFIC_ERROR - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeInPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeInPacketTest.groovy deleted file mode 100644 index 18da8e4f..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/in/UnsubscribeInPacketTest.groovy +++ /dev/null @@ -1,77 +0,0 @@ -package com.ss.mqtt.broker.test.network.in - -import com.ss.mqtt.broker.model.PacketProperty -import com.ss.mqtt.broker.network.packet.in.UnsubscribeInPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class UnsubscribeInPacketTest extends BaseInPacketTest { - - def "should read packet correctly as mqtt 3.1.1"() { - - given: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putString(topicFilter) - it.putString(topicFilter2) - } - - when: - def packet = new UnsubscribeInPacket(0b1011_0000 as byte) - def result = packet.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.packetId == packetId - packet.userProperties == Array.empty() - } - - def "should read packet correctly as mqtt 5.0"() { - - given: - - def propertiesBuffer = BufferUtils.prepareBuffer(512) { - it.putProperty(PacketProperty.USER_PROPERTY, userProperties) - } - - def dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(propertiesBuffer.limit()) - it.put(propertiesBuffer) - it.putString(topicFilter) - it.putString(topicFilter2) - } - - when: - def packet = new UnsubscribeInPacket(0b1011_0000 as byte) - def result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.packetId == packetId - packet.userProperties == userProperties - when: - - dataBuffer = BufferUtils.prepareBuffer(512) { - it.putShort(packetId) - it.putMbi(0) - it.putString(topicFilter) - it.putString(topicFilter2) - } - - packet = new UnsubscribeInPacket(0b1011_0000 as byte) - result = packet.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - then: - result - packet.topicFilters.size() == 2 - packet.topicFilters.get(0).toString() == topicFilter - packet.topicFilters.get(1).toString() == topicFilter2 - packet.packetId == packetId - packet.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Authentication5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Authentication5OutPacketTest.groovy deleted file mode 100644 index 8fd6a670..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Authentication5OutPacketTest.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.AuthenticateReasonCode -import com.ss.mqtt.broker.network.packet.in.AuthenticationInPacket -import com.ss.mqtt.broker.network.packet.out.Authentication5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class Authentication5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Authentication5OutPacket( - userProperties, - AuthenticateReasonCode.CONTINUE_AUTHENTICATION, - reasonString, - authMethod, - authData, - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new AuthenticationInPacket(0b1111_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == AuthenticateReasonCode.CONTINUE_AUTHENTICATION - reader.authenticationMethod == authMethod - reader.authenticationData == authData - reader.reason == reasonString - reader.userProperties == userProperties - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/BaseOutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/BaseOutPacketTest.groovy deleted file mode 100644 index 51b50748..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/BaseOutPacketTest.groovy +++ /dev/null @@ -1,30 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.network.client.MqttClient -import com.ss.mqtt.broker.test.network.NetworkUnitSpecification -import spock.lang.Shared - -class BaseOutPacketTest extends NetworkUnitSpecification { - - @Shared - MqttClient mqtt5Client = Stub(MqttClient.UnsafeMqttClient) { - getConnectionConfig() >> mqttConnectionConfig - getSessionExpiryInterval() >> NetworkUnitSpecification.sessionExpiryInterval - getReceiveMax() >> NetworkUnitSpecification.receiveMaximum - getMaximumPacketSize() >> NetworkUnitSpecification.maximumPacketSize - getClientId() >> clientId - getKeepAlive() >> serverKeepAlive - getTopicAliasMaximum() >> NetworkUnitSpecification.topicAliasMaximum - } - - @Shared - MqttClient mqtt311Client = Stub(MqttClient.UnsafeMqttClient) { - getConnectionConfig() >> mqttConnectionConfig - getSessionExpiryInterval() >> NetworkUnitSpecification.sessionExpiryInterval - getReceiveMax() >> NetworkUnitSpecification.receiveMaximum - getMaximumPacketSize() >> NetworkUnitSpecification.maximumPacketSize - getClientId() >> clientId - getKeepAlive() >> serverKeepAlive - getTopicAliasMaximum() >> NetworkUnitSpecification.topicAliasMaximum - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect311OutPacketTest.groovy deleted file mode 100644 index c31086da..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect311OutPacketTest.groovy +++ /dev/null @@ -1,47 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.network.packet.in.ConnectInPacket -import com.ss.mqtt.broker.network.packet.out.Connect311OutPacket -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class Connect311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Connect311OutPacket( - userName, - "", - clientId, - userPassword, - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - willRetain, - cleanStart, - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new ConnectInPacket(0b0001_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.username == userName - reader.clientId == clientId - reader.password == userPassword - reader.keepAlive == keepAlive - reader.userProperties == Array.empty() - reader.cleanStart == cleanStart - reader.willRetain == willRetain - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect5OutPacketTest.groovy deleted file mode 100644 index f516f93e..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Connect5OutPacketTest.groovy +++ /dev/null @@ -1,63 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.network.packet.in.ConnectInPacket -import com.ss.mqtt.broker.network.packet.out.Connect5OutPacket -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils - -class Connect5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Connect5OutPacket( - userName, - "", - clientId, - userPassword, - ArrayUtils.EMPTY_BYTE_ARRAY, - QoS.AT_MOST_ONCE, - keepAlive, - willRetain, - cleanStart, - userProperties, - authMethod, - authData, - sessionExpiryInterval, - receiveMaximum, - maximumPacketSize, - topicAliasMaximum, - requestResponseInformation, - requestProblemInformation - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new ConnectInPacket(0b0001_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.username == userName - reader.clientId == clientId - reader.password == userPassword - reader.keepAlive == keepAlive - reader.userProperties == userProperties - reader.cleanStart == cleanStart - reader.willRetain == willRetain - reader.authenticationMethod == authMethod - reader.authenticationData == authData - reader.sessionExpiryInterval == sessionExpiryInterval - reader.receiveMax == receiveMaximum - reader.maximumPacketSize == maximumPacketSize - reader.topicAliasMaximum == topicAliasMaximum - reader.requestResponseInformation == requestResponseInformation - reader.requestProblemInformation == requestProblemInformation - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck311OutPacketTest.groovy deleted file mode 100644 index 88bc847a..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck311OutPacketTest.groovy +++ /dev/null @@ -1,52 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import com.ss.mqtt.broker.network.packet.out.ConnectAck311OutPacket -import javasabr.rlib.common.util.ArrayUtils -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class ConnectAck311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new ConnectAck311OutPacket( - ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, - sessionPresent - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new ConnectAckInPacket(0b0010_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - reader.sessionPresent == sessionPresent - reader.assignedClientId == "" - reader.reason == "" - reader.userProperties == Array.empty() - reader.retainAvailable == MqttPropertyConstants.RETAIN_AVAILABLE_DEFAULT - reader.wildcardSubscriptionAvailable == MqttPropertyConstants.WILDCARD_SUBSCRIPTION_AVAILABLE_DEFAULT - reader.subscriptionIdAvailable == MqttPropertyConstants.SUBSCRIPTION_IDENTIFIER_AVAILABLE_DEFAULT - reader.sharedSubscriptionAvailable == MqttPropertyConstants.SHARED_SUBSCRIPTION_AVAILABLE_DEFAULT - reader.responseInformation == "" - reader.serverReference == "" - reader.authenticationData == ArrayUtils.EMPTY_BYTE_ARRAY - reader.authenticationMethod == "" - reader.topicAliasMaximum == MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED - reader.serverKeepAlive == MqttPropertyConstants.SERVER_KEEP_ALIVE_UNDEFINED - reader.receiveMax == MqttPropertyConstants.RECEIVE_MAXIMUM_UNDEFINED - reader.sessionExpiryInterval == MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED - reader.maximumPacketSize == MqttPropertyConstants.MAXIMUM_PACKET_SIZE_UNDEFINED - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck5OutPacketTest.groovy deleted file mode 100644 index d9ac0561..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/ConnectAck5OutPacketTest.groovy +++ /dev/null @@ -1,71 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.ConnectAckReasonCode -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.network.packet.in.ConnectAckInPacket -import com.ss.mqtt.broker.network.packet.out.ConnectAck5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class ConnectAck5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new ConnectAck5OutPacket( - ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD, - sessionPresent, - "-1", - MqttPropertyConstants.SESSION_EXPIRY_INTERVAL_UNDEFINED, - MqttPropertyConstants.SERVER_KEEP_ALIVE_UNDEFINED, - MqttPropertyConstants.TOPIC_ALIAS_MAXIMUM_UNDEFINED, - reasonString, - serverReference, - responseInformation, - authMethod, - authData, - userProperties, - clientId, - maxQos, - sessionExpiryInterval, - maximumPacketSize, - receiveMaximum, - topicAliasMaximum, - serverKeepAlive, - retainAvailable, - wildcardSubscriptionAvailable, - subscriptionIdAvailable, - sharedSubscriptionAvailable - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new ConnectAckInPacket(0b0010_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == ConnectAckReasonCode.BAD_USER_NAME_OR_PASSWORD - reader.sessionPresent == sessionPresent - reader.retainAvailable == retainAvailable - reader.sessionExpiryInterval == sessionExpiryInterval - reader.receiveMax == receiveMaximum - reader.maximumPacketSize == maximumPacketSize - reader.assignedClientId == clientId - reader.topicAliasMaximum == topicAliasMaximum - reader.reason == reasonString - reader.userProperties == userProperties - reader.wildcardSubscriptionAvailable == wildcardSubscriptionAvailable - reader.subscriptionIdAvailable == subscriptionIdAvailable - reader.sharedSubscriptionAvailable == sharedSubscriptionAvailable - reader.serverKeepAlive == serverKeepAlive - reader.responseInformation == responseInformation - reader.serverReference == serverReference - reader.authenticationData == authData - reader.authenticationMethod == authMethod - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/DisconnectAck5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/DisconnectAck5OutPacketTest.groovy deleted file mode 100644 index 937be7b4..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/DisconnectAck5OutPacketTest.groovy +++ /dev/null @@ -1,39 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.DisconnectReasonCode -import com.ss.mqtt.broker.network.packet.in.DisconnectInPacket -import com.ss.mqtt.broker.network.packet.out.Disconnect5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class DisconnectAck5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Disconnect5OutPacket( - DisconnectReasonCode.PACKET_TOO_LARGE, - userProperties, - reasonString, - serverReference, - sessionExpiryInterval - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new DisconnectInPacket(0b1110_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == DisconnectReasonCode.PACKET_TOO_LARGE - reader.userProperties == userProperties - reader.reason == reasonString - reader.serverReference == serverReference - reader.sessionExpiryInterval == sessionExpiryInterval - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish311OutPacketTest.groovy deleted file mode 100644 index a2f5a402..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish311OutPacketTest.groovy +++ /dev/null @@ -1,68 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.network.packet.in.PublishInPacket -import com.ss.mqtt.broker.network.packet.out.Publish311OutPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class Publish311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new Publish311OutPacket( - packetId, - QoS.EXACTLY_ONCE, - true, - true, - publishTopic.toString(), - publishPayload - ) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishInPacket(0b0011_1101 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == packetId - reader.qos == QoS.EXACTLY_ONCE - reader.retained - reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties == Array.empty() - when: - - packet = new Publish311OutPacket( - packetId, - QoS.AT_MOST_ONCE, - false, - false, - publishTopic.toString(), - publishPayload - ) - - dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - reader = new PublishInPacket(0b0011_0000 as byte) - result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == 0 - reader.qos == QoS.AT_MOST_ONCE - !reader.retained - !reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties == Array.empty() - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish5OutPacketTest.groovy deleted file mode 100644 index 1925dce6..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Publish5OutPacketTest.groovy +++ /dev/null @@ -1,85 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.QoS -import com.ss.mqtt.broker.network.packet.in.PublishInPacket -import com.ss.mqtt.broker.network.packet.out.Publish5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class Publish5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new Publish5OutPacket( - packetId, - QoS.EXACTLY_ONCE, - true, - true, - publishTopic.toString(), - publishPayload, - topicAlias, - false, - responseTopic, - correlationData, - userProperties - ) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishInPacket(0b0011_1101 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == packetId - reader.qos == QoS.EXACTLY_ONCE - reader.retained - reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties == userProperties - reader.topicAlias == topicAlias - !reader.payloadFormatIndicator - reader.responseTopic == responseTopic - reader.correlationData == correlationData - when: - - packet = new Publish5OutPacket( - packetId, - QoS.AT_MOST_ONCE, - false, - false, - publishTopic.toString(), - publishPayload, - topicAlias, - true, - responseTopic, - correlationData, - userProperties - ) - - dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - reader = new PublishInPacket(0b0011_0000 as byte) - result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == 0 - reader.qos == QoS.AT_MOST_ONCE - !reader.retained - !reader.duplicate - reader.payload == publishPayload - reader.topicName == publishTopic - reader.userProperties == userProperties - reader.topicAlias == topicAlias - reader.payloadFormatIndicator - reader.responseTopic == responseTopic - reader.correlationData == correlationData - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck311OutPacketTest.groovy deleted file mode 100644 index 630a1655..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck311OutPacketTest.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket -import com.ss.mqtt.broker.network.packet.out.PublishAck311OutPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishAck311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new PublishAck311OutPacket(packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishAckInPacket(0b0100_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishAckReasonCode.SUCCESS - reader.packetId == packetId - reader.userProperties == Array.empty() - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck5OutPacketTest.groovy deleted file mode 100644 index 5299d59f..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishAck5OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishAckReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishAckInPacket -import com.ss.mqtt.broker.network.packet.out.PublishAck5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class PublishAck5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new PublishAck5OutPacket( - packetId, - PublishAckReasonCode.NOT_AUTHORIZED, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishAckInPacket(0b0100_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishAckReasonCode.NOT_AUTHORIZED - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete311OutPacketTest.groovy deleted file mode 100644 index 888c7c53..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete311OutPacketTest.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket -import com.ss.mqtt.broker.network.packet.out.PublishComplete311OutPacket -import javasabr.rlib.common.util.BufferUtils -import javasabr.rlib.collections.array.Array - -class PublishComplete311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new PublishComplete311OutPacket(packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishCompleteInPacket(0b0111_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishCompletedReasonCode.SUCCESS - reader.packetId == packetId - reader.userProperties == Array.empty() - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete5OutPacketTest.groovy deleted file mode 100644 index 6206eded..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishComplete5OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishCompletedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishCompleteInPacket -import com.ss.mqtt.broker.network.packet.out.PublishComplete5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class PublishComplete5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new PublishComplete5OutPacket( - packetId, - PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishCompleteInPacket(0b0111_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishCompletedReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived311OutPacketTest.groovy deleted file mode 100644 index c3110d8f..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived311OutPacketTest.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket -import com.ss.mqtt.broker.network.packet.out.PublishReceived311OutPacket -import javasabr.rlib.collections.array.Array -import javasabr.rlib.common.util.BufferUtils - -class PublishReceived311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new PublishReceived311OutPacket(packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishReceivedInPacket(0b0101_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishReceivedReasonCode.SUCCESS - reader.packetId == packetId - reader.userProperties == Array.empty(StringPair) - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived5OutPacketTest.groovy deleted file mode 100644 index 310917e1..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishReceived5OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishReceivedReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReceivedInPacket -import com.ss.mqtt.broker.network.packet.out.PublishReceived5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class PublishReceived5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new PublishReceived5OutPacket( - packetId, - PublishReceivedReasonCode.UNSPECIFIED_ERROR, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishReceivedInPacket(0b0101_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishReceivedReasonCode.UNSPECIFIED_ERROR - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease311OutPacketTest.groovy deleted file mode 100644 index 3d8fc00b..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease311OutPacketTest.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket -import com.ss.mqtt.broker.network.packet.out.PublishRelease311OutPacket -import javasabr.rlib.collections.array.Array -import javasabr.rlib.common.util.BufferUtils - -class PublishRelease311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new PublishRelease311OutPacket(packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishReleaseInPacket(0b0110_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishReleaseReasonCode.SUCCESS - reader.packetId == packetId - reader.userProperties == Array.empty(StringPair) - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease5OutPacketTest.groovy deleted file mode 100644 index 2f9f357d..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/PublishRelease5OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.reason.code.PublishReleaseReasonCode -import com.ss.mqtt.broker.network.packet.in.PublishReleaseInPacket -import com.ss.mqtt.broker.network.packet.out.PublishRelease5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class PublishRelease5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new PublishRelease5OutPacket( - packetId, - PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new PublishReleaseInPacket(0b0110_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCode == PublishReleaseReasonCode.PACKET_IDENTIFIER_NOT_FOUND - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe311OutPacketTest.groovy deleted file mode 100644 index c3a5c328..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe311OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.network.packet.in.SubscribeInPacket -import com.ss.mqtt.broker.network.packet.out.Subscribe311OutPacket -import javasabr.rlib.collections.array.Array -import javasabr.rlib.common.util.BufferUtils - -class Subscribe311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Subscribe311OutPacket( - topicFiltersObj311, - 1 - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new SubscribeInPacket(0b1000_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == 1 - reader.topicFilters == topicFiltersObj311 - reader.userProperties == Array.empty(StringPair) - reader.subscriptionId == MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe5OutPacketTest.groovy deleted file mode 100644 index dd503d59..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/Subscribe5OutPacketTest.groovy +++ /dev/null @@ -1,37 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.MqttPropertyConstants -import com.ss.mqtt.broker.network.packet.in.SubscribeInPacket -import com.ss.mqtt.broker.network.packet.out.Subscribe5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class Subscribe5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new Subscribe5OutPacket( - topicFiltersObj5, - 1, - userProperties, - MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new SubscribeInPacket(0b1000_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.packetId == 1 - reader.topicFilters == topicFiltersObj5 - reader.userProperties == userProperties - reader.subscriptionId == MqttPropertyConstants.SUBSCRIPTION_ID_UNDEFINED - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck311OutPacketTest.groovy deleted file mode 100644 index 958beef5..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck311OutPacketTest.groovy +++ /dev/null @@ -1,31 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.SubscribeAck311OutPacket -import javasabr.rlib.collections.array.Array -import javasabr.rlib.common.util.BufferUtils - -class SubscribeAck311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new SubscribeAck311OutPacket(subscribeAckReasonCodes, packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new SubscribeAckInPacket(0b1001_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCodes == subscribeAckReasonCodes - reader.packetId == packetId - reader.userProperties == Array.empty(StringPair) - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck5OutPacketTest.groovy deleted file mode 100644 index 69cb13c3..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/SubscribeAck5OutPacketTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.network.packet.in.SubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.SubscribeAck5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class SubscribeAck5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new SubscribeAck5OutPacket( - packetId, - subscribeAckReasonCodes, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new SubscribeAckInPacket(0b1001_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCodes == subscribeAckReasonCodes - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck311OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck311OutPacketTest.groovy deleted file mode 100644 index c97cf73a..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck311OutPacketTest.groovy +++ /dev/null @@ -1,32 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.model.data.type.StringPair -import com.ss.mqtt.broker.model.reason.code.UnsubscribeAckReasonCode -import com.ss.mqtt.broker.network.packet.in.UnsubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.UnsubscribeAck311OutPacket -import javasabr.rlib.collections.array.Array -import javasabr.rlib.common.util.BufferUtils - -class UnsubscribeAck311OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - def packet = new UnsubscribeAck311OutPacket(packetId) - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new UnsubscribeAckInPacket(0b1011_0000 as byte) - def result = reader.read(mqtt311Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCodes == Array.empty(UnsubscribeAckReasonCode) - reader.packetId == packetId - reader.userProperties == Array.empty(StringPair) - reader.reason == "" - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck5OutPacketTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck5OutPacketTest.groovy deleted file mode 100644 index 05997ec6..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/network/out/UnsubscribeAck5OutPacketTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package com.ss.mqtt.broker.test.network.out - -import com.ss.mqtt.broker.network.packet.in.UnsubscribeAckInPacket -import com.ss.mqtt.broker.network.packet.out.UnsubscribeAck5OutPacket -import javasabr.rlib.common.util.BufferUtils - -class UnsubscribeAck5OutPacketTest extends BaseOutPacketTest { - - def "should write packet correctly"() { - - given: - - def packet = new UnsubscribeAck5OutPacket( - packetId, - unsubscribeAckReasonCodes, - userProperties, - reasonString - ) - - when: - - def dataBuffer = BufferUtils.prepareBuffer(512) { - packet.write(it) - } - - def reader = new UnsubscribeAckInPacket(0b1011_0000 as byte) - def result = reader.read(mqtt5Connection, dataBuffer, dataBuffer.limit()) - - then: - result - reader.reasonCodes == unsubscribeAckReasonCodes - reader.packetId == packetId - reader.userProperties == userProperties - reader.reason == reasonString - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/util/MqttDataUtilsTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/util/MqttDataUtilsTest.groovy deleted file mode 100644 index a73b3736..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/util/MqttDataUtilsTest.groovy +++ /dev/null @@ -1,62 +0,0 @@ -package com.ss.mqtt.broker.test.util - -import com.ss.mqtt.broker.test.UnitSpecification -import com.ss.mqtt.broker.util.MqttDataUtils - -import java.nio.ByteBuffer - -class MqttDataUtilsTest extends UnitSpecification { - - def "should write integer to MQTT multi byte integer successful"(int value, int expectedBytes) { - given: - def buffer = ByteBuffer.allocate(4) - when: - MqttDataUtils.writeMbi(value, buffer) - then: - buffer.position() == expectedBytes - where: - value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] - expectedBytes << [1, 2, 3, 3, 3, 4] - } - - def "should failed writing too big integer to MQTT multi byte integer"(int value) { - given: - def buffer = ByteBuffer.allocate(10) - when: - MqttDataUtils.writeMbi(value, buffer) - then: - thrown IllegalArgumentException - where: - value << [1_000_000_000, 2_000_000_000, 5_000_000_000] - } - - def "should read integer from MQTT multi byte integer successful"(int value) { - given: - def buffer = ByteBuffer.allocate(5) - MqttDataUtils.writeMbi(value, buffer).flip() - when: - def read = MqttDataUtils.readMbi(buffer) - then: - read == value - where: - value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] - } - - def "should failed reading integer from MQTT multi byte integer"(int value, int position) { - given: - - def buffer = ByteBuffer.allocate(10) - - MqttDataUtils.writeMbi(value, buffer) - .position(position) - .flip() - - when: - def read = MqttDataUtils.readMbi(buffer) - then: - read == -1 - where: - value << [10, 1000, 40_000, 500_000, 1_000_000, MqttDataUtils.MAX_MBI] - position << [0, 0, 1, 1, 2, 2] - } -} diff --git a/src/test/groovy/com/ss/mqtt/broker/test/util/TopicUtilsTest.groovy b/src/test/groovy/com/ss/mqtt/broker/test/util/TopicUtilsTest.groovy deleted file mode 100644 index 904cba9a..00000000 --- a/src/test/groovy/com/ss/mqtt/broker/test/util/TopicUtilsTest.groovy +++ /dev/null @@ -1,57 +0,0 @@ -package com.ss.mqtt.broker.test.util - - -import spock.lang.Specification -import spock.lang.Unroll - -import static com.ss.mqtt.broker.util.TopicUtils.* - -class TopicUtilsTest extends Specification { - - @Unroll - def "should create valid topic name: [#topicName]"() { - expect: - !isInvalid(buildTopicName(topicName)) - where: - topicName | _ - "topic/Name" | _ - "topic" | _ - } - - @Unroll - def "should create valid topic filter: [#topicFilter]"() { - expect: - !isInvalid(buildTopicFilter(topicFilter)) - where: - topicFilter | _ - "topic/Filter" | _ - "topic/+" | _ - "topic/+/Filter" | _ - "topic/#" | _ - } - - @Unroll - def "should detect invalid topic name: [#topicName]"() { - expect: - isInvalid(buildTopicName(topicName)) - where: - topicName | _ - "topic/+" | _ - "topic/" | _ - "topic//Name" | _ - "topic/#" | _ - } - - @Unroll - def "should detect invalid topic filter: [#topicFilter]"() { - expect: - isInvalid(buildTopicFilter(topicFilter)) - where: - topicFilter | _ - "topic/" | _ - "/topic" | _ - "topic//Name" | _ - "topic/##" | _ - "#/Filter" | _ - } -} diff --git a/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule b/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule deleted file mode 100644 index 4c16efe3..00000000 --- a/src/test/resources/META-INF/services/org.codehaus.groovy.runtime.ExtensionModule +++ /dev/null @@ -1,3 +0,0 @@ -moduleName = test-specification -moduleVersion = 1.0 -extensionClasses =com.ss.mqtt.broker.test.extension.SpecificationExtensions diff --git a/test-support/build.gradle b/test-support/build.gradle new file mode 100644 index 00000000..25689237 --- /dev/null +++ b/test-support/build.gradle @@ -0,0 +1,14 @@ +plugins { + id "java-library" +} + +dependencies { + api libs.hivemq.mqtt.client + api libs.moquette.broker + api libs.spring.test + api libs.spock.core + api libs.spock.spring + api libs.groovy.all + api libs.byte.buddy.dep + api libs.objenesis +} \ No newline at end of file